C#扩展方法与Lambda表达式基本用法

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. 最佳实践

  1. ​命名规范​​:

    • 扩展方法名应清晰表明其功能
    • 避免与现有方法名冲突
  2. ​静态类组织​​:

    • 按功能分组相关扩展方法
    • 考虑按扩展类型命名静态类(如StringExtensions
  3. ​文档注释​​:

    cs 复制代码
    /// <summary>
    /// 检查字符串是否为null或空
    /// </summary>
    /// <param name="str">要检查的字符串</param>
    /// <returns>如果字符串为null或空则返回true</returns>
    public static bool IsNullOrEmpty(this string str)
  4. ​性能考虑​​:

    • 避免在扩展方法中执行昂贵操作
    • 考虑延迟执行(如使用yield return
  5. ​避免过度使用​​:

    • 不应滥用扩展方法来"修复"不良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

    cs 复制代码
    x => 
    {
        // 多行逻辑
        return x * 2;
    }

2. 可读性

  • 保持Lambda简短(通常不超过2-3行)

  • 复杂逻辑提取为命名方法

    cs 复制代码
    Func<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#代码的表达力和简洁性,同时保持良好的可维护性和性能。在实际开发中,应根据具体场景选择最合适的模式和技术。

相关推荐
martian66526 分钟前
信创系统图形界面开发指南:技术选择与实践详解
开发语言·科技·系统架构·系统安全·创业创新
我命由我1234527 分钟前
STM32 开发 - stm32f10x.h 头文件(内存映射、寄存器结构体与宏、寄存器位定义、实现点灯案例)
c语言·开发语言·c++·stm32·单片机·嵌入式硬件·嵌入式
ghost1431 小时前
C#学习第20天:垃圾回收
开发语言·学习·c#
oioihoii1 小时前
C++23 std::invoke_r:调用可调用 (Callable) 对象 (P2136R3)
开发语言·c++23
一眼青苔1 小时前
conda添加新python版本环境,如何激活和销毁
开发语言·python·conda
Pseudo…1 小时前
web技术与Nginx网站服务
开发语言·php
咛辉1 小时前
spark和Hadoop之间的对比和联系
开发语言
Humbunklung2 小时前
Sigmoid函数简介及其Python实现
开发语言·python·深度学习·机器学习
__lost2 小时前
MATLAB退火算法和遗传算法解决旅行商问题
开发语言·算法·matlab·遗传算法·退火算法
恶霸不委屈2 小时前
MATLAB函数调用全解析:从入门到精通
开发语言·算法·matlab·匿名函数·函数句柄