C# 中的函数编程:实用部分
C# 中的函数编程:实用部分
函数式编程模式常常给人一种学术性和抽象的感觉。"单子"(monads)和"函子"(functors)这样的术语往往会吓退许多开发者。但在这些令人生畏的术语背后,其实隐藏着一些能让代码更安全、更易维护的实用模式。
C#多年来已经采纳了许多函数式编程特性:
- Records用于实现不可变性
- LIQ用于函数式转换
- Lambda表达式实现一等函数
这些特性不仅仅是语法糖 — 它们能帮助预防bug,使代码更容易理解。
让我们来看看今天就能在C#项目中使用的五种实用模式。
高阶函数 高阶函数可以接收其他函数作为参数或将函数作为结果返回。它们让你能够编写更灵活和可组合的代码,因为你可以像传递数据一样传递行为。
高阶函数的常见例子是LIQ中的Where和Select,它们接收用于转换数据的函数。
让我们用高阶函数重构这个验证示例:
代码语言:javascript代码运行次数:0运行复制public classOrderValidator
{
publicboolValidateOrder(Order order)
{
if(order.Items.Count ==)returnfalse;
if(order.TotalAmount <=)returnfalse;
if(order.ShippingAddress ==null)returnfalse;
returntrue;
}
}
// What if we need:
// - different validation rules for different countries?
// - to reuse some validati but not others?
// - to combine validati differently?
这里展示了如何使用高阶函数使其更灵活:
代码语言:javascript代码运行次数:0运行复制public staticclassOrderValidation
{
publicstaticFunc<Order, bool>CreateValidator(string countryCode,decimal minimumOrderValue)
{
var baseValidati =CombineValidati(
o => o.Items.Count >,
o => o.TotalAmount >= minimumOrderValue,
o => o.ShippingAddress !=null
);
return countryCode switch
{
"US"=>CombineValidati(
baseValidati,
order =>IsValidUSAddress(order.ShippingAddress)),
"EU"=>CombineValidati(
baseValidati,
order =>IsValidVATumber(order.Vatumber)),
_ => baseValidati
};
}
privatestaticFunc<Order, bool>CombineValidati(paramsFunc<Order, bool>[] validati)=>
order => validati.All(v =>v(order));
}
// Usage
var usValidator = OrderValidation.CreateValidator("US",minimumOrderValue:25.0m);
var euValidator = OrderValidation.CreateValidator("EU",minimumOrderValue:0.0m);
高阶函数方法使验证器变得可组合、可测试且易于扩展。每个验证规则都是一个简单的函数,我们可以将它们组合起来。
将错误作为值 C#中的错误处理通常是这样的:
代码语言:javascript代码运行次数:0运行复制public classUserService
{
publicUserCreateUser(string email,string password)
{
if(string.IsullOrEmpty(email))
{
thrownewArgumentException("Email is required");
}
if(password.Length <)
{
thrownewArgumentException("Password too short");
}
if(_userRepository.EmailExists(email))
{
thrownewDuplicateEmailException(email);
}
// Create user...
}
}
存在的问题?
- 异常处理代价高昂
- 调用者常常忘记处理异常
- 方法签名具有欺骗性 — 它声称返回User但可能会抛出异常
我们可以使用OneOf库使错误变得明确。它为C#提供了判别联合,使用自定义类型OneOf<T0, ... Tn>。
代码语言:javascript代码运行次数:0运行复制public classUserService
{
publicOneOf<User, ValidationError, DuplicateEmailError>CreateUser(string email,string password)
{
if(string.IsullOrEmpty(email))
{
returnnewValidationError("Email is required");
}
if(password.Length <)
{
returnnewValidationError("Password too short");
}
if(_userRepository.EmailExists(email))
{
returnnewDuplicateEmailError(email);
}
returnnewUser(email, password);
}
}
通过使错误明确化:
- 方法签名完全说明了真相
- 调用者必须处理所有可能的结果
- 没有异常带来的性能开销
- 流程更容易理解
使用方式如下:
代码语言:javascript代码运行次数:0运行复制var result = userService.CreateUser(email, password);
result.Switch(
user => SendWelcomeEmail(user),
validationError => HandleError(validationError),
duplicateError => HandleError(duplicateError)
);
单子绑定 单子是值的容器 — 像List、IEnumerable或Task。它的特别之处在于你可以对容器内的值进行链式操作,而无需直接处理容器。这种链式操作称为单子绑定。
你每天都在使用LIQ时都在使用单子绑定,只是可能不知道。它允许我们链式操作来转换数据。
Map (Select) 转换值:
代码语言:javascript代码运行次数:0运行复制// Simple transformati with Select (Map)
var numbers = new[] { , , , };
var doubled = numbers.Select(x => x * );
Bind (SelectMany) 转换并展平:
代码语言:javascript代码运行次数:0运行复制// Operati that return multiple values use SelectMany (Bind)
var folders = new[] { "docs", "photos" };
var files = folders.SelectMany(folder => Directory.GetFiles(folder));
在实践中应用单子的一个流行例子是Result模式,它提供了一种清晰的方式来链接可能失败的操作。
纯函数 纯函数是可预测的:它们只依赖于输入,不会改变系统中的任何东西。没有数据库调用,没有API请求,没有全局状态。这种约束使它们更容易理解、测试和调试。
代码语言:javascript代码运行次数:0运行复制// Impure - relies on hidden state
publicclassPriceCalculator
{
privatedecimal _taxRate;
privateList<Discount> _activeDiscounts;
publicdecimalCalculatePrice(Order order)
{
var price = order.Items.Sum(i => i.Price);
foreach(var discount in _activeDiscounts)
{
price -= discount.Calculate(price);
}
return price *(+ _taxRate);
}
}
这是同样的例子作为纯函数:
代码语言:javascript代码运行次数:0运行复制// Pure - everything is explicit
publicstaticclassPriceCalculator
{
publicstaticdecimalCalculatePrice(
Order order,
decimal taxRate,
IReadOnlyList<Discount> discounts)
{
var basePrice = order.Items.Sum(i => i.Price);
var afterDiscounts = discounts.Aggregate(
basePrice,
(price, discount)=> price - discount.Calculate(price));
return afterDiscounts *(+ taxRate);
}
}
纯函数是线程安全的,易于测试,并且易于理解,因为所有依赖都是显式的。
不可变性 不可变对象在创建后不能被更改。相反,它们为每个更改创建新的实例。这个简单的约束消除了整类bug:竞态条件、意外修改和不一致状态。
这是一个可变类型的例子:
代码语言:javascript代码运行次数:0运行复制public classOrder
{
publicList<OrderItem> Items {get;set;}
publicdecimal Total {get;set;}
publicOrderStatus Status {get;set;}
publicvoidAddItem(OrderItem item)
{
Items.Add(item);
Total += item.Price;
// Bug: Thread safety issues
// Bug: Can modify shipped orders
// Bug: Total might not match Items
}
}
让我们将其改造为不可变类型:
代码语言:javascript代码运行次数:0运行复制public recordOrder
{
publicImmutableList<OrderItem> Items {get;init;}
publicOrderStatus Status {get;init;}
publicdecimal Total => Items.Sum(x => x.Price);
publicOrderAddItem(OrderItem item)
{
if(Status != OrderStatus.Created)
{
thrownewInvalidOperationException("Can't modify shipped orders");
}
returnthiswith
{
Items = Items.Add(item)
};
}
}
不可变版本的特点:
- 默认线程安全
- 使无效状态变得不可能
- 保持数据和计算的一致性
- 使更改明确且可追踪
函数式编程不仅仅是关于写"更干净"的代码。这些模式从根本上改变了你处理复杂性的方式:
- 将错误推送到编译时 — 在运行代码之前捕获问题
- 使无效状态变得不可能 — 不依赖文档或约定
- 使正确路径明显 — 当一切都是显式的,流程就很清晰
你可以逐步采用这些模式。从一个类、一个模块、一个功能开始。目标不是写纯函数式代码。目标是写出更安全、更可预测、更易维护的代码。
本文参与 腾讯云自媒体同步曝光计划,分享自。原始发表:2025-01-16,如有侵权请联系 cloudcommunity@tencent 删除编程函数函数式编程异常c##感谢您对电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格的认可,转载请说明来源于"电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格
推荐阅读
留言与评论(共有 16 条评论) |
本站网友 人生如梦幻泡影 | 6分钟前 发表 |
让我们来看看今天就能在C#项目中使用的五种实用模式 | |
本站网友 嘉兴二手房网 | 8分钟前 发表 |
bool>[] validati)=> order => validati.All(v =>v(order)); } // Usage var usValidator = OrderValidation.CreateValidator("US" | |
本站网友 关于面瘫 | 24分钟前 发表 |
将错误作为值 C#中的错误处理通常是这样的:代码语言:javascript代码运行次数:0运行复制public classUserService { publicUserCreateUser(string email | |
本站网友 慈溪婚纱照 | 29分钟前 发表 |
本文参与 腾讯云自媒体同步曝光计划 | |
本站网友 66800 | 10分钟前 发表 |
}; var doubled = numbers.Select(x => x * ); Bind (SelectMany) 转换并展平:代码语言:javascript代码运行次数:0运行复制// Operati that return multiple values use SelectMany (Bind) var folders = new[] { "docs" | |
本站网友 幻想之痛 | 3分钟前 发表 |
测试和调试 | |
本站网友 rsi指标详解 | 4分钟前 发表 |
其实隐藏着一些能让代码更安全 | |
本站网友 罗死了吗 | 29分钟前 发表 |
高阶函数的常见例子是LIQ中的Where和Select | |
本站网友 求职招聘论坛 | 21分钟前 发表 |
因为所有依赖都是显式的 | |
本站网友 第一张大字报 | 4分钟前 发表 |
o => o.TotalAmount >= minimumOrderValue | |
本站网友 百酷 | 18分钟前 发表 |
"photos" }; var files = folders.SelectMany(folder => Directory.GetFiles(folder)); 在实践中应用单子的一个流行例子是Result模式 | |
本站网友 我应该干什么 | 30分钟前 发表 |
_ => baseValidati }; } privatestaticFunc<Order | |
本站网友 挪威森林 | 27分钟前 发表 |
0.0m); 高阶函数方法使验证器变得可组合 | |
本站网友 资产转移 | 19分钟前 发表 |
它允许我们链式操作来转换数据 | |
本站网友 富力湾 | 1分钟前 发表 |
它们接收用于转换数据的函数 |