C# 扩展方法与 Lambda 表达式详解
一、扩展方法详解
1. 基本概念
扩展方法允许为现有类型"添加"方法,而无需修改原始类型或创建派生类型。
定义条件:
- 必须在静态类中定义
- 方法本身必须是静态的
- 第一个参数使用
this
修饰符指定要扩展的类型
示例:
cs
public static class StringExtensions
{
// 扩展string类型的方法
public static bool IsNullOrEmpty(this string str)
{
return string.IsNullOrEmpty(str);
}
// 扩展int类型的方法
public static bool IsEven(this int number)
{
return number % 2 == 0;
}
}
// 使用
string s = "test";
Console.WriteLine(s.IsNullOrEmpty()); // False
int num = 4;
Console.WriteLine(num.IsEven()); // True
2. 高级用法
(1) 扩展泛型方法
cs
public static class EnumerableExtensions
{
public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T> source) where T : class
{
return source.Where(item => item != null);
}
public static IEnumerable<T?> WhereNotNull<T>(this IEnumerable<T?> source) where T : struct
{
return source.Where(item => item.HasValue).Select(item => item.Value);
}
}
// 使用
var strings = new List<string?> { "a", null, "b" };
var nonNullStrings = strings.WhereNotNull(); // 返回 "a", "b"
(2) 扩展索引器
cs
public static class DictionaryExtensions
{
public static TValue GetOrDefault<TKey, TValue>(
this IDictionary<TKey, TValue> dictionary,
TKey key,
TValue defaultValue = default)
{
return dictionary.TryGetValue(key, out var value) ? value : defaultValue;
}
}
// 使用
var dict = new Dictionary<string, int> { { "one", 1 }, { "two", 2 } };
Console.WriteLine(dict.GetOrDefault("one")); // 1
Console.WriteLine(dict.GetOrDefault("three", 0)); // 0
(3) 扩展异步方法
cs
public static class AsyncExtensions
{
public static async Task<T> WithTimeout<T>(this Task<T> task, TimeSpan timeout)
{
using var timeoutCancellationTokenSource = new CancellationTokenSource();
var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token));
if (completedTask == task)
{
timeoutCancellationTokenSource.Cancel();
return await task;
}
throw new TimeoutException("The operation has timed out.");
}
}
// 使用
var result = await SomeAsyncOperation().WithTimeout(TimeSpan.FromSeconds(5));
3. 最佳实践
-
命名规范:
- 扩展方法名应清晰表明其功能
- 避免与现有方法名冲突
-
静态类组织:
- 按功能分组相关扩展方法
- 考虑按扩展类型命名静态类(如
StringExtensions
)
-
文档注释:
cs/// <summary> /// 检查字符串是否为null或空 /// </summary> /// <param name="str">要检查的字符串</param> /// <returns>如果字符串为null或空则返回true</returns> public static bool IsNullOrEmpty(this string str)
-
性能考虑:
- 避免在扩展方法中执行昂贵操作
- 考虑延迟执行(如使用
yield return
)
-
避免过度使用:
- 不应滥用扩展方法来"修复"不良API设计
- 优先考虑继承或组合等更明确的设计模式
二、Lambda表达式详解
1. 基本语法
基本形式:
(参数列表) => 表达式或语句块
示例:
cs
// 表达式Lambda
Func<int, int> square = x => x * x;
// 语句Lambda
Action<string> greet = name =>
{
Console.WriteLine($"Hello, {name}!");
};
greet("World"); // 输出: Hello, World!
2. 类型推断
编译器可以自动推断Lambda表达式的参数类型:
cs
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var evenNumbers = numbers.Where(n => n % 2 == 0); // n自动推断为int
3. 多参数Lambda
cs
Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(3, 5)); // 输出: 8
4. 无参数Lambda
cs
Action sayHello = () => Console.WriteLine("Hello!");
sayHello(); // 输出: Hello!
5. 返回值Lambda
cs
Func<int, int, int> multiply = (x, y) =>
{
int result = x * y;
return result;
};
6. 复杂Lambda表达式
cs
Func<int, bool> isEven = x =>
{
if (x % 2 == 0)
return true;
else
return false;
};
三、扩展方法与Lambda结合使用
1. 在扩展方法中使用Lambda
cs
public static class EnumerableExtensions
{
public static IEnumerable<T> WhereIf<T>(
this IEnumerable<T> source,
bool condition,
Func<T, bool> predicate)
{
return condition ? source.Where(predicate) : source;
}
}
// 使用
var numbers = new List<int> { 1, 2, 3, 4, 5 };
var result = numbers.WhereIf(true, x => x > 2); // 返回3,4,5
2. 高阶函数返回Lambda
cs
public static class FunctionFactory
{
public static Func<int, int> CreateMultiplier(int factor)
{
return x => x * factor;
}
}
// 使用
var double = FunctionFactory.CreateMultiplier(2);
Console.WriteLine(double(5)); // 输出: 10
3. 延迟执行与Lambda
cs
public static class LazyExtensions
{
public static IEnumerable<T> LazySelect<T>(
this IEnumerable<T> source,
Func<T, T> selector)
{
foreach (var item in source)
{
yield return selector(item);
}
}
}
// 使用
var numbers = new List<int> { 1, 2, 3 };
var lazyNumbers = numbers.LazySelect(x => x * 10); // 不立即执行
foreach (var num in lazyNumbers) // 此时才执行
{
Console.WriteLine(num); // 输出: 10, 20, 30
}
四、Lambda表达式的高级特性
1. 表达式树(Expression Trees)
cs
using System.Linq.Expressions;
Expression<Func<int, bool>> expr = x => x > 5;
Console.WriteLine(expr); // 输出: x => (x > 5)
// 编译表达式
Func<int, bool>> compiled = expr.Compile();
Console.WriteLine(compiled(6)); // 输出: True
2. 捕获外部变量
cs
int factor = 10;
Func<int, int> multiplier = x => x * factor;
Console.WriteLine(multiplier(5)); // 输出: 50
注意:捕获的变量会被提升到闭包中
3. 方法组转换
cs
public class Calculator
{
public int Add(int a, int b) => a + b;
}
// 方法组转换为委托
Calculator calc = new Calculator();
Func<int, int, int> addDelegate = calc.Add;
Console.WriteLine(addDelegate(3, 4)); // 输出: 7
4. 匿名方法与Lambda对比
cs
// 匿名方法
Func<int, int> square1 = delegate (int x) { return x * x; };
// Lambda表达式
Func<int, int> square2 = x => x * x;
Lambda优势:
- 更简洁的语法
- 自动类型推断
- 支持表达式树
五、性能考虑
1. Lambda vs 方法
cs
// 方法
private static int Add(int a, int b) => a + b;
// 使用方法
Func<int, int, int> addMethod = Add;
// 使用Lambda
Func<int, int, int> addLambda = (a, b) => a + b;
性能差异:
- 方法调用通常略快(无闭包开销)
- Lambda在简单情况下与方法性能相当
- 复杂Lambda可能有轻微性能开销
2. 闭包优化
cs
// 不推荐 - 创建多个闭包
List<Func<int>> funcs = new List<Func<int>>();
for (int i = 0; i < 10; i++)
{
funcs.Add(() => i); // 捕获循环变量i
}
// 推荐 - 创建局部副本
for (int i = 0; i < 10; i++)
{
int copy = i;
funcs.Add(() => copy); // 捕获局部变量copy
}
六、调试与测试
1. Lambda调试技巧
cs
// 添加调试信息
Func<int, int> debugLambda = x =>
{
Console.WriteLine($"Input: {x}");
return x * 2;
};
2. 单元测试Lambda
cs
[Fact]
public void TestLambda()
{
// Arrange
Func<int, int> square = x => x * x;
// Act
var result = square(5);
// Assert
Assert.Equal(25, result);
}
七、常见陷阱与解决方案
1. 变量捕获问题
问题:
cs
var funcs = new List<Func<int>>();
for (int i = 0; i < 3; i++)
{
funcs.Add(() => i); // 所有lambda都捕获同一个i
}
// 结果都是3
解决方案:
cs
var funcs = new List<Func<int>>();
for (int i = 0; i < 3; i++)
{
int copy = i;
funcs.Add(() => copy); // 每个lambda捕获自己的copy
}
// 结果0,1,2
2. 可变性陷阱
问题:
cs
bool flag = false;
Action toggle = () => flag = !flag;
toggle();
Console.WriteLine(flag); // true
解决方案:
cs
// 如果需要不可变行为,使用局部变量
void SetupToggle(out Action toggle)
{
bool flag = false;
toggle = () => flag = !flag; // 编译错误 - flag必须是final/readonly
// 正确做法:使用闭包或重构设计
}
3. 性能陷阱
问题:
cs
// 每次调用都创建新委托
Func<int, int> CreateMultiplier(int factor)
{
return x => x * factor; // 每次调用都创建新闭包
}
解决方案:
cs
// 缓存委托
private static readonly Dictionary<int, Func<int, int>> _multipliers =
new Dictionary<int, Func<int, int>>();
Func<int, int> CreateMultiplier(int factor)
{
if (!_multipliers.TryGetValue(factor, out var multiplier))
{
multiplier = x => x * factor;
_multipliers[factor] = multiplier;
}
return multiplier;
}
八、最佳实践
1. 命名规范
-
简单Lambda:
x => x * 2
-
复杂Lambda:使用大括号和return
csx => { // 多行逻辑 return x * 2; }
2. 可读性
-
保持Lambda简短(通常不超过2-3行)
-
复杂逻辑提取为命名方法
csFunc<int, int> complexCalc = x => CalculateSomething(x); private static int CalculateSomething(int x) { // 复杂逻辑 return x * 2; }
3. 错误处理
cs
Func<string, int> safeParse = s =>
{
if (int.TryParse(s, out var result))
return result;
throw new ArgumentException("Invalid number format");
};
4. 延迟执行
cs
public static IEnumerable<T> Filter<T>(
this IEnumerable<T> source,
Func<T, bool> predicate)
{
foreach (var item in source)
{
if (predicate(item))
yield return item;
}
}
九、扩展方法与Lambda结合的高级模式
1. 流式API设计
cs
public static class QueryExtensions
{
public static IQueryable<T> WhereIf<T>(
this IQueryable<T> source,
bool condition,
Expression<Func<T, bool>> predicate)
{
return condition ? source.Where(predicate) : source;
}
public static IQueryable<T> OrderByProperty<T>(
this IQueryable<T> source,
string propertyName)
{
var parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(parameter, propertyName);
var lambda = Expression.Lambda(property, parameter);
var methodName = "OrderBy";
var method = typeof(Queryable).GetMethods()
.First(m => m.Name == methodName &&
m.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), property.Type);
return (IQueryable<T>)method.Invoke(null, new object[] { source, lambda });
}
}
// 使用
var query = dbContext.Products
.WhereIf(showActiveOnly, p => p.IsActive)
.OrderByProperty("Price");
2. 函数组合
cs
public static class FunctionCombinators
{
public static Func<T, Z> Compose<T, Y, Z>(
this Func<T, Y> f,
Func<Y, Z> g)
{
return x => g(f(x));
}
}
// 使用
Func<int, int> square = x => x * x;
Func<int, int> increment = x => x + 1;
Func<int, int> squareThenIncrement = increment.Compose(square);
Console.WriteLine(squareThenIncrement(3)); // (3 * 3)+1 = 10
3. 惰性求值
cs
public static class LazyExtensions
{
public static IEnumerable<T> LazySelect<T>(
this IEnumerable<T> source,
Func<T, T> selector)
{
foreach (var item in source)
{
yield return selector(item);
}
}
}
// 使用
var numbers = new List<int> { 1, 2, 3 };
var lazyNumbers = numbers.LazySelect(x =>
{
Console.WriteLine($"Processing {x}");
return x * 10;
});
// 此时不会执行
foreach (var num in lazyNumbers) // 执行时才处理
{
Console.WriteLine(num);
}
十、实际应用示例
1. 数据库查询构建器
cs
public class QueryBuilder<T>
{
private readonly List<Expression<Func<T, bool>>> _predicates = new();
public QueryBuilder<T> Where(Expression<Func<T, bool>> predicate)
{
_predicates.Add(predicate);
return this;
}
public IQueryable<T> Build(IQueryable<T> source)
{
IQueryable<T> query = source;
foreach (var predicate in _predicates)
{
query = query.Where(predicate);
}
return query;
}
}
// 使用
var queryBuilder = new QueryBuilder<Product>()
.Where(p => p.Price > 100)
.Where(p => p.Category == "Electronics");
var query = queryBuilder.Build(dbContext.Products);
2. 链式验证器
cs
public static class ValidatorExtensions
{
public static IValidator<T> Ensure<T>(
this IValidator<T> validator,
Func<T, bool> condition,
string errorMessage)
{
return new ConditionalValidator<T>(validator, condition, errorMessage);
}
}
public class ConditionalValidator<T> : IValidator<T>
{
private readonly IValidator<T> _inner;
private readonly Func<T, bool> _condition;
private readonly string _errorMessage;
public ConditionalValidator(
IValidator<T> inner,
Func<T, bool> condition,
string errorMessage)
{
_inner = inner;
_condition = condition;
_errorMessage = errorMessage;
}
public ValidationResult Validate(T instance)
{
var result = _inner.Validate(instance);
if (_condition(instance) && !result.IsValid)
{
result.Errors.Add(new ValidationFailure("", _errorMessage));
}
return result;
}
}
// 使用
var validator = new BasicValidator<User>()
.Ensure(u => u.Age < 18, "未成年人不允许注册")
.Ensure(u => !string.IsNullOrEmpty(u.Email), "邮箱不能为空");
通过合理使用扩展方法和Lambda表达式,可以显著提高C#代码的表达力和简洁性,同时保持良好的可维护性和性能。在实际开发中,应根据具体场景选择最合适的模式和技术。