一篇文章厘清C#中的lambda表达式

一篇文章厘清C#中的lambda表达式

链接: 源码

说C#的匿名函数,就要先说一下匿名函数.

1 lambda表达式演变史

C# 匿名函数的演变历史可以追溯到 C# 语言的不同版本,随着语言特性的不断丰富和发展,匿名函数经历了以下几个主要阶段:

1. C# 1.0 (2002)

在 C# 1.0 中,虽然还没有直接支持匿名函数的概念,但已经引入了委托(Delegate)这一关键概念。委托允许将方法作为参数传递或存储为变量,为后续匿名函数的引入奠定了基础。在这个版本中,若要创建委托实例,必须先定义一个具有匹配签名的方法,然后使用该方法的名称来初始化委托。

例如:

public delegate int MyDelegate(int x, int y);

public static int AddNumbers(int a, int b)
{
    
    
    return a + b;
}

MyDelegate add = new MyDelegate(AddNumbers);

2. C# 2.0 (2005)

C# 2.0 引入了匿名方法,这是对匿名函数功能的初步实现。匿名方法允许开发者在需要委托的地方直接编写一段代码块(内联),而无需事先定义一个命名方法。这种语法简化了在特定上下文中临时创建和使用简单功能的过程,特别是在事件处理和回调场景中。

匿名方法的语法如下:

MyDelegate add = delegate(int a, int b)
{
    
    
    return a + b;
};

3. C# 3.0 (2007)

C# 3.0 引入了更强大的匿名函数形式——Lambda 表达式。Lambda 表达式进一步简化了匿名方法的语法,使其更加简洁且易于阅读。Lambda 表达式可以直接表示输入参数、箭头符号(=>)以及要执行的表达式或语句块。它们在LINQ(Language Integrated Query)中扮演了核心角色,极大地增强了C#的函数式编程能力。

Lambda 表达式的语法示例:

// 单行表达式形式
MyDelegate add = (int a, int b) => a + b;

// 多行语句块形式
MyDelegate process = (int a, int b) =>
{
    
    
    int result = a * b;
    Console.WriteLine("Processing numbers...");
    return result;
};

4. C# 4.0及以后

随着Lambda表达式的普及和广泛使用,匿名方法在新项目中的使用逐渐减少,Lambda表达式成为编写匿名函数的首选方式。后续版本的C#(如4.0、5.0、6.0、7.0、8.0、9.0、10.0等)继续强化和扩展了Lambda表达式的能力,包括:

  • 类型推断:Lambda表达式中的参数类型可以根据上下文自动推断,进一步减少了代码冗余。
  • 可变数量参数:Lambda表达式支持可变数量参数,方便处理不定长度的参数列表。
  • async/await支持:C# 支持异步Lambda表达式,便于编写非阻塞的异步代码。
  • Expression-bodied members:C# 6.0引入了表达式体成员语法,使得Lambda风格的简短表达式可以用于方法、属性、构造函数等更多场景。
  • Local functions(局部函数):虽然不是匿名函数,但C# 7.0引入的局部函数提供了另一种在方法内部定义私有、嵌套函数的方式,有时可以作为匿名函数的替代方案,尤其是在需要复用或避免闭包副作用的情况下。

综上所述,C# 匿名函数的演变历史始于C# 2.0的匿名方法,经由C# 3.0的Lambda表达式实现了重大飞跃,并在后续版本中持续得到增强和完善,成为现代C#编程中不可或缺的一部分。尽管匿名方法在早期版本中有其作用,但在当前实践中,Lambda表达式已成为编写匿名函数的标准方式。

2 lambda表达式使用方法

C# Lambda 表达式是一种简洁、灵活的匿名函数表示形式,广泛应用于各种编程场景,如 LINQ 查询、事件处理、委托实例化、高阶函数应用等。以下是使用 C# Lambda 表达式的一些基本方法和常见用例:

1 基本语法

(input-parameters) => { <sequence-of-statements> }

Lambda 表达式的语法由三部分组成:

  1. 参数列表:位于圆括号 () 内,可以为空(表示无参数)、包含一个或多个参数,参数类型可以显式声明或根据上下文推断。

    • 显式类型:(int x, string y)
    • 类型推断:(x, y) —— 当Lambda表达式赋值给已知委托类型或在编译器可以确定类型的情境下,可以省略参数类型。
  2. 箭头操作符 =>:将参数列表与表达式或语句块分隔开。

  3. 表达式或语句块:表示Lambda表达式的行为。

    • 单行表达式:直接返回表达式的计算结果,不需要使用 return 关键字。

      int[] numbers = {
              
               1, 2, 3 };
      var evenNumbers = numbers.Where(n => n % 2 == 0);
      
    • 多行语句块:使用花括号 {} 包围,如果需要执行多条语句或需要显式 return 语句,则使用语句块形式。

      Func<int, int> squareAndLog = number =>
      {
              
              
          int squared = number * number;
          Console.WriteLine($"Squared: {
                
                squared}");
          return squared;
      };
      

2 使用场景和示例

作为参数

Lambda 表达式常被用作需要函数作为参数的方法或操作符的参数,如 LINQ 方法、事件处理器、ActionFunc 委托实例化等。

// LINQ 查询中的 Where 方法
var filteredItems = items.Where(item => item.IsAvailable && item.Price > 100);

// 使用 Action 委托
Action<string> logMessage = message => Console.WriteLine($"Logging: {
      
      message}");
logMessage("Hello, Lambda!");

// 使用 Func 委托
Func<int, int, int> add = (a, b) => a + b;
int sum = add(3, 5);

匿名委托

Lambda 表达式可以替代传统的匿名方法,创建不需显式定义的、临时使用的委托实例。

// 传统匿名方法
button.Click += delegate (object sender, EventArgs e)
{
    
    
    MessageBox.Show("Button clicked!");
};

// Lambda 表达式形式
button.Click += (sender, e) => MessageBox.Show("Button clicked!");

LINQ 查询表达式

Lambda 表达式是 LINQ 查询语法的核心组成部分,用于定义筛选、投影、聚合等操作。

var customers = context.Customers
                      .Where(c => c.Country == "USA")
                      .Select(c => new {
    
     Name = c.Name, TotalPurchases = c.Purchases.Sum(p => p.Amount) })
                      .OrderByDescending(x => x.TotalPurchases);

事件处理

Lambda 表达式简化了事件处理器的注册,尤其在需要访问外部变量时,可以利用闭包特性。

string greeting = "Welcome!";
button.Click += (sender, e) => MessageBox.Show(greeting);

并行编程与任务调度

Lambda 表达式在 Task.RunParallel.ForEach 等并行编程场景中非常有用。

// 创建并启动一个后台任务
Task.Run(() =>
{
    
    
    DoSomeWork();
    UpdateUI();
});

// 并行处理集合
Parallel.ForEach(numbers, number =>
{
    
    
    if (IsPrime(number))
        primes.Add(number);
});

延迟执行与 Lazy

Lambda 表达式用于初始化 Lazy<T> 对象,确保资源仅在首次访问时才进行计算或加载。

Lazy<int> expensiveComputation = new Lazy<int>(() => ComputeExpensiveValue());
int result = expensiveComputation.Value; // 计算仅在此处发生

3 注意事项

  • 类型推断:Lambda 表达式的参数类型和返回类型通常可以由编译器推断,无需显式声明。但在某些情况下,可能需要显式提供类型信息以消除歧义。

  • 闭包:Lambda 表达式可以捕获其封闭作用域内的变量,形成闭包。理解闭包行为对于避免潜在的并发问题和资源管理问题至关重要。

  • 性能:Lambda 表达式通常编译为高效代码,但在某些情况下(如大型循环中的复杂Lambda表达式),可能会导致编译器生成额外的类和方法,影响性能。适当优化或使用局部函数替代可能有助于提升效率。

总之,C# Lambda 表达式提供了简洁、直观的方式来编写匿名函数,极大地提高了代码的可读性和可维护性,尤其在处理函数式编程、事件处理、委托、LINQ 查询等方面发挥着重要作用。理解和熟练运用Lambda表达式是现代C#开发中的重要技能。

3 系统自带的两种委托:Action和Func

C# 中的 ActionFunc 是预定义的泛型委托类型,它们简化了委托的使用,避免了手动声明相似用途的自定义委托。今后我们使用时,没有必要自定义委托了,全部使用系统自带的委托就可以了,方便省事.

下面分别介绍 ActionFunc 的用法:

Action

Action 代表一个无返回值的方法,只用于封装需要执行的操作。根据需要传递的参数数量,C# 提供了一系列预定义的 Action 类型,从 Action(无参数)到 Action<typeparamref name="T1">, ..., T16</typeparamref></typeparamref></typeparamref>(最多16个参数)。

用法示例

  • 无参数

    Action noParamAction = () => Console.WriteLine("No parameter action called.");
    noParamAction(); // 输出 "No parameter action called."
    
  • 带参数

    Action<string, int> paramAction = (message, count) => Console.WriteLine($"{
            
            message}, count: {
            
            count}");
    paramAction("Action with parameters", 5); // 输出 "Action with parameters, count: 5"
    

应用场景

  • 事件处理:作为事件处理器,执行某种操作,无需返回值。

    button.Click += (sender, e) => Console.WriteLine("Button clicked!");
    
  • 回调:传递给异步操作或其他方法作为完成时的回调函数。

    Task.Run(() => LongRunningOperation())
        .ContinueWith(_ => Console.WriteLine("Long running operation completed."));
    

Func

Func 代表一个有返回值的方法,除了封装操作外,还返回一个指定类型的值。Func 类型同样有一系列预定义版本,格式为 Func<typeparamref name="T1">, ..., Tn</typeparamref>, TResult>,其中 T1Tn 代表输入参数类型,TResult 代表返回值类型。

用法示例

  • 无参数,返回整数

    Func<int> noParamFunc = () => DateTime.Now.Second;
    int currentSecond = noParamFunc(); // 获取当前秒数
    
  • 带参数,返回字符串

    Func<int, string, string> paramFunc = (num, text) => $"{
            
            text} {
            
            num}";
    string combined = paramFunc(42, "The answer is"); // 结果为 "The answer is 42"
    

应用场景

  • LINQ 查询:作为查询表达式中的选择器(Select)、谓词(Where)等方法的参数。

    List<int> numbers = new List<int> {
          
           1, 2, 3, 4, 5 };
    var squares = numbers.Select(n => n * n); // 返回平方数的集合
    
  • 工厂方法:传递给需要动态创建对象的函数。

    Func<string, MyClass> factory = name => new MyClass {
          
           Name = name };
    MyClass instance = factory("Instance created by Func");
    
  • 计算或转换:在需要简单计算或数据转换的场景中作为参数传递。

    double average = CalculateAverage(scores, score => score / 10.0); // 转换分数为小数
    

总结来说,Action 用于封装无需返回值的操作,常用于事件处理、回调等场景;而 Func 用于封装带有返回值的方法,适用于需要计算、转换或作为函数参数返回结果的情况。两者均通过泛型参数来适应不同数量和类型的参数需求,大大简化了委托的使用。

4 综合案列

{    //.netframework 1.0
    MyDelgate myDelgate = new MyDelgate(MyFunc);
    myDelgate("keson");

    void MyFunc(string s)
    {
        Console.WriteLine($"hello,{s}");
    }
}

{    //.netframework 2.0
    MyDelgate myDelgate = new MyDelgate(
        delegate (string s)
        {
            Console.WriteLine($"hello,{s}");
        });
    myDelgate("keson");
}

{    //.netframework 3.0
    MyDelgate myDelgate = new MyDelgate(
         (string s) =>
        {
            Console.WriteLine($"hello,{s}");
        });
    myDelgate("keson");
}

{//省略参数关键字
    MyDelgate2 myDelgate = new MyDelgate2(
     (s, n) =>
     {
         Console.WriteLine($"hello,{s},I am {n} years old");
     });
    myDelgate("keson", 18);
}

{ //省略方法体的大括号
    MyDelgate2 myDelgate = new MyDelgate2(
     (s, n) =>
         Console.WriteLine($"hello,{s},I am {n} years old"));

    myDelgate("keson", 18);
}

{//匿名类
    object ImplicitClass = new
    {
        Id = 1,
        Name = "jack"
    };
    Console.WriteLine(ImplicitClass.GetType().Name);
    //C#是强类型语言,编译时会确定类型,ImplicitClass编译时是object类型,
    //object类本身没有Id属性,所以无法访问
    //Console.WriteLine(ImplicitClass.Id);

    //dynamic避开编译器检查
    dynamic ImplicitClass2 = new
    {
        Id = 99,
        Name = "keson"
    };
    Console.WriteLine(ImplicitClass2.Id);
    Console.WriteLine(ImplicitClass2.GetType().Name);
    // ImplicitClass2.瞎写();//编译时不检查,但是运行时会抛出异常.

    var ImplicitClass3 = new
    {
        Id = 89,
        Name = "keson",
        Age = 18
    };
    Console.WriteLine(ImplicitClass3.Id);
    Console.WriteLine(ImplicitClass3.GetType().Name);
    
    //ImplicitClass3.Id = 1; //匿名类是只读类型,只能初始化是设置
}

{//使用系统自带的委托action和func
    Action action = () => { Console.WriteLine("无参"); }; //无入参
    Action<string> action2 = s => { Console.WriteLine($"有参: {s}"); };//有入参
    Func<string> func = () => { return "keson"; };//无入参,有返回值
    Func<int,int> func2 = i => { return i * i; };//有入参,有返回值
    Func<int, int> func3 = i =>  i + i;//当方法体只有一行,关键字return可以省略,大括号可以省略
    Console.WriteLine(func3(3));
}

public delegate void MyDelgate(string s);
public delegate void MyDelgate2(string s, int num);

猜你喜欢

转载自blog.csdn.net/weixin_45589116/article/details/137697448