C# Lambda 表达式 详解

总目录


前言

在C#编程中,Lambda表达式是一种简洁而强大的语法特性,它提供了一种更加灵活和直观的方式来编写匿名函数。无论是在LINQ查询、事件处理还是异步编程中,Lambda表达式都扮演着重要角色。本文将详细介绍Lambda,帮助您更好地理解和掌握这一重要的C#特性。


一、Lambda 表达式 是什么?

1. 定义

Lambda表达式是C# 3.0引入的一个新特性,它是匿名方法的简化形式,允许开发者以更简洁的方式定义函数。Lambda表达式可以用于任何需要委托的地方,并且可以在运行时动态创建函数对象。它的主要优点包括代码简洁、易于阅读和维护。

2. 基本语法

Lambda表达式的语法非常简单,由参数列表、=>符号和表达式或语句块组成。其基本形式如下:

csharp 复制代码
(parameters) => expression

csharp 复制代码
(parameters) => { statements }
  • 参数列表:可以包含零个或多个参数,用逗号分隔。
  • =>符号 :称为"goes to"操作符,表示从参数到表达式或语句块的映射。
  • 表达式或语句块:可以是一个简单的表达式或一个包含多条语句的代码块。

二、如何使用 Lambda 表达式?

根据是否包含语句块,Lambda表达式可以分为两种类型:表达式Lambda语句Lambda

1. 使用 表达式Lambda

表达式Lambda是最简单的形式,仅包含一个表达式表达式Lambda 侧重于表达式的返回值。其语法如下:

csharp 复制代码
(parameters) => expression

//如:
 x => x * 4

1)演化过程

csharp 复制代码
public int Square(int num)
{
     return num * num;
}

上面定义一个简单的计算方法,现在我们需要以 Lambda 表达式表现出来,该如何表现呢?

csharp 复制代码
Func<int, int> func = x => x * x;

上述两个示例代码是等效的, x => x * x; 本质上等同于将一个有返回值的方法赋予了Func委托;

2)示例

csharp 复制代码
// 定义一个委托类型
Func<int, int> square = x => x * x;

// 调用委托
Console.WriteLine(square(5)); // 输出: 25

在这个例子中,我们定义了一个名为 square 的委托,它接受一个整数参数并返回该整数的平方值。通过Lambda表达式,我们可以非常简洁地实现这一功能。

2. 语句Lambda

语句Lambda允许包含多条语句,适用于需要执行复杂逻辑的情况。其语法如下:

csharp 复制代码
(parameters) => { statements }

语句Lambda侧重语句块中执行内容,具体示例如下:

csharp 复制代码
// 定义一个委托类型
Action<int> printAndSquare = x =>
{
    Console.WriteLine($"Input: {x}");
    Console.WriteLine($"Square: {x * x}");
};

// 调用委托
printAndSquare(5); // 输出: Input: 5 Square: 25

在这个例子中,我们定义了一个名为 printAndSquare 的委托,它接受一个整数参数并打印输入值及其平方值。通过语句Lambda,我们可以轻松实现这一复杂的逻辑。

csharp 复制代码
Func<int, int> factorial = n =>
{
    if (n == 0)
        return 1;
    else
        return n * factorial(n - 1);
};

3. Lambda 表达式的各种使用方式

1)无参数 与 空括号

将 lambda 表达式的输入参数括在括号中。 使用空括号指定零个输入参数。如果是没有参数,必须有这个空括号

csharp 复制代码
Action line = () => Console.WriteLine();

2)方法体只有一行代码

如果方法体中只有一行代码,可以省略方法体大括号

csharp 复制代码
Action line1 = () => { Console.WriteLine("test"); };

//省略方法体大括号
Action line2 = () => Console.WriteLine("test");

3)类型推断

C# 编译器会根据上下文自动推断参数类型,无需显式声明:

Lambda表达式的类型是由编译器根据上下文推断出来的,通常对应于某个委托类型(如 Func<T, TResult>Action<T>)。如果Lambda表达式没有返回值,则其类型为 Action;如果有返回值,则其类型为 Func

csharp 复制代码
Func<int, int, int> func3 = (int num1, int num2) => num1 * num2;

//类型推断 允许我们省略 数据类型的显示声明
Func<int, int, int> func4 = (num1, num2) => num1 * num2;

4)无法类型推断时

一般情况下,编译器可自行推断输入参数的类型,但存在编译器无法推断的情况,则需自行指定

csharp 复制代码
Func<int, string, bool> isTooLong = (int x, string s) => s.Length > x;

5)省略return

如果方法体中只有一行代码,且有返回值,可以省略return

  • 当方法体中只有一行代码,但并没有返回值的时候,此时的Lambda表达式 是 【语句 Lambda
  • 当方法体中只有一行代码,且有返回值的时候,此时的Lambda表达式 是 【表达式 Lambda
csharp 复制代码
// 语句 Lambda ,侧重于执行内容
Action action = () => Console.WriteLine("ss");

// 表达式 Lambda,侧重于返回值
// 特征:一行代码 + 有返回值
Func<int, int> func1 = x => x * x * x;
Func<int, string> func2 = x => x.ToString();      

6)一个参数

如果只有一个输入参数,则括号可有可无

csharp 复制代码
Func<int, int> func = (x) => x * x * x;

//省略括号
Func<double, double> cube = x => x * x * x;

7) 2个参数以上

两个或更多输入参数使用逗号加以分隔

csharp 复制代码
Func<int, int, bool> testForEquality = (x, y) => x == y;

Func<int, int, int> add = (x, y) => x + y;

8)多行代码 + 显示 return

当方法体中具有多行代码,则 该Lambda表达式 是【语句Lambda】,如果需要返回数据,则需要显示使用return

csharp 复制代码
Func<int, int, int> func3 = (num1, num2) =>
{
    Console.WriteLine("计算:");
    return num1* num2;
};                                                

三、Lambda 表达式的常见应用场景

1. 应用场景

Lambda表达式广泛应用于各种场景,以下是几个常见的应用场景:

  • LINQ查询:在LINQ查询中,Lambda表达式常用于指定过滤条件、投影、排序等操作。
  • 事件处理:可以用Lambda表达式来简化事件处理程序的编写。
  • 异步编程:在异步编程中,Lambda表达式可用于定义异步任务的执行逻辑。
  • 委托与回调:Lambda表达式可以作为委托的实现,用于回调函数或其他需要传递函数指针的场合。

2. 应用场景示例

1)Lambda与委托

  • 内置委托类型 :Lambda表达式可隐式转换为FuncAction委托:

    csharp 复制代码
    // Func委托(带返回值)  
    Func<int, int, int> add = (a, b) => a + b;  
    
    // Action委托(无返回值)  
    Action<string> log = msg => Console.WriteLine(msg);  

    Lambda 表达式可以转换的委托类型由其参数和返回值的类型定义。 如果 Lambda 表达式不返回值,则可以将其转换为 Action 委托类型之一;否则,可将其转换为 Func 委托类型之一。

  • 自定义委托 :Lambda可直接赋值给自定义委托类型:

    csharp 复制代码
    delegate int Operation(int a, int b);
    Operation add = (x, y) => x + y;
    Console.WriteLine(add(3, 4)); // 输出 7

2)Lambda 在LINQ中的应用

Lambda是LINQ查询的基石,是编写查询条件的标准方式,用于定义筛选、排序和转换逻辑:

csharp 复制代码
var numbers = new List<int> { 1, 2, 3, 4, 5 };  
// 筛选大于3的元素  
var filtered = numbers.Where(n => n > 3);  // 筛选 >3 的数
// 转换为平方值  
var squares = numbers.Select(n => n * n);  // 转换为平方值
csharp 复制代码
public class Program
{
    public static void Main()
    {
        var numbers = new List<int> { 1, 2, 3, 4, 5 };

        // 筛选
        var evenNumbers = numbers.Where(x => x % 2 == 0);
        Console.WriteLine("Even numbers: " + string.Join(", ", evenNumbers));

        // 排序
        var sortedNumbers = numbers.OrderBy(x => x);
        Console.WriteLine("Sorted numbers: " + string.Join(", ", sortedNumbers));

        // 投影
        var squaredNumbers = numbers.Select(x => x * x);
        Console.WriteLine("Squared numbers: " + string.Join(", ", squaredNumbers));
    }
}
  • 表达式树:Lambda还可生成表达式树,供LINQ to SQL等框架解析为SQL语句。

3) Lambda 在事件处理中的应用

Lambda 可简化事件处理程序的编写:

csharp 复制代码
button.Click += (sender, e) => Console.WriteLine("按钮被点击!");

4)Lambda 在异步编程中的应用

在异步编程中,Lambda表达式可以用于定义异步任务的执行逻辑。

csharp 复制代码
class Program
{
    static async Task Main()
    {
        await Task.Run(() =>
        {
            Console.WriteLine("Task started.");
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine($"Processing item {i + 1}");
            }
            Console.WriteLine("Task completed.");
        });

        Console.WriteLine("Main method completed.");
    }
}

在这个例子中,我们使用Lambda表达式 () => { ... } 来定义异步任务的执行逻辑。

三、高级特性

1. 闭包(Closure)

Lambda 表达式可以捕获上下文中(或 外部作用域 )的变量,形成闭包。

Lambda表达式可以捕获其定义范围内的局部变量,这意味着Lambda表达式可以访问外部作用域中的变量。这种特性使得Lambda表达式在某些情况下非常强大和灵活。

csharp 复制代码
public class Program
{
    public static void Main()
    {
        int factor = 2;
        Func<int, int> multiply = x => x * factor;
        Console.WriteLine(multiply(5)); // 输出: 10

        factor = 5;
        Console.WriteLine(multiply(5)); // 输出: 25(闭包保留了 factor 的引用)
    }
}

在这个例子中,Lambda表达式捕获了外部变量 factor ,并在计算结果时使用了这个变量的值。

需要注意的是,被捕获的变量在Lambda表达式被调用时仍然有效,因此要确保这些变量在其生命周期内不会被销毁或修改。

注意事项

  • 捕获的变量生命周期与委托绑定,可能导致内存泄漏;
  • 避免修改外部变量,防止逻辑混乱。
  • 闭包可能导致意外的变量共享,需谨慎处理。

2. 表达式树(Expression Tree)

Lambda 表达式可以转换为表达式树,用于表示代码的结构化形式。表达式树常用于动态生成代码或进行反射操作。

csharp 复制代码
public class Program
{
    public static void Main()
    {
        Expression<Func<int, int>> expression = x => x + 1;
        Console.WriteLine(expression.ToString()); // 输出: x => (x + 1)
    }
}

3. 异步 Lambda

Lambda 表达式可以用于异步操作,通过 asyncawait 关键字实现。

csharp 复制代码
Func<Task<int>> asyncLambda = async () => await Task.Delay(1000).ContinueWith(_ => 42);
csharp 复制代码
using System;
using System.Threading.Tasks;

public class Program
{
    public static void Main()
    {
        Func<int, Task<int>> asyncLambda = async x =>
        {
            await Task.Delay(1000);
            return x * x;
        };

        asyncLambda(5).ContinueWith(t => Console.WriteLine(t.Result)); // 输出: 25
    }
}
csharp 复制代码
Func<Task> asyncTask = async () => 
{  
    await Task.Delay(1000);  
    Console.WriteLine("异步完成");  
};  

4. 动态方法

Lambda 表达式可以用于动态生成方法,这在反射或动态调用中非常有用。

csharp 复制代码
using System;
using System.Reflection.Emit;

public class Program
{
    public static void Main()
    {
        // 动态生成一个方法
        DynamicMethod dynamicMethod = new DynamicMethod("Add", typeof(int), new[] { typeof(int), typeof(int) }, typeof(Program).Module);
        ILGenerator il = dynamicMethod.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Add);
        il.Emit(OpCodes.Ret);

        // 调用动态生成的方法
        Func<int, int, int> add = (Func<int, int, int>)dynamicMethod.CreateDelegate(typeof(Func<int, int, int>));
        Console.WriteLine(add(2, 3)); // 输出: 5
    }
}

5. 泛型 Lambda

Lambda 表达式可以用于泛型委托,提高代码的复用性增强灵活性。

csharp 复制代码
Func<T, string> toString = (T obj) => obj?.ToString();  

四、注意事项与限制

  • 作用域规则

    • Lambda 无法捕获 ref/out 参数。
    • Lambda 内的return不影响外部方法。
    • 外部方法无法访问 Lambda 内部定义的变量。
  • 运算符限制

    • 不可用于isas运算符左侧。
  • 语句限制

    • 不允许包含 gotobreakcontinue 跨越 Lambda 边界的语句。
  • 性能考量

    • 表达式树可能比直接委托生成更多内存开销。
    • 频繁调用的Lambda需避免闭包捕获,减少GC压力。
    • 过度使用Lambda表达式可能影响性能,尤其是在循环中频繁创建委托时。
    • Lambda 表达式在运行时会被编译为委托或表达式树,可能会有一定的性能开销。在性能敏感的场景中,需要谨慎使用。
  • 循环变量捕获

    • Lambda表达式捕获循环变量时,可能会导致意外行为(多线程异步情况下)。应使用局部变量来避免。
    csharp 复制代码
    for (int i = 0; i < 3; i++)
    {
        int local = i;
        actions.Add(() => Console.WriteLine(local));
    }

五、演化过程

csharp 复制代码
    public delegate void ShowDelegate(int a,string b);
    public class LambdaEvolution
    {
        public void Show(int a1,string b1)
        {
            Console.WriteLine($"show{a1}:{b1}");
        }

        public void Test()
        {
            //1 .Netframework1.0/1.1,原始方法
            ShowDelegate showDelegate = new ShowDelegate(Show);

            //2 .NetFramework2.0,匿名方法,增加delegate,去掉单独定义方法
            showDelegate = delegate (int x,string y) { Console.WriteLine($"show{x}:{y}"); };

            //3 .NetFramework3.0,=> 引入Lambda表达式,去掉delegate
            showDelegate = (int s,string t) => { Console.WriteLine($"show{s}:{t}"); };

            //4 .NetFramework3.0后期,简化参数类型,编译器自动推断数据类型
            showDelegate = (s,t) => { Console.WriteLine($"show{s}:{t}"); };

            //5 如果方法体中只有一行代码,可以省略方法体大括号
            showDelegate = (s,t) => Console.WriteLine($"show{s}:{t}");

            //如果方法只有一个参数,可以省略参数小括号
            Action<string> action = x => Console.WriteLine($"show{x}");

            //如果方法体中只有一行代码,且有返回值,可以省略return
            Func<int, string> func = x => x.ToString();

        }

    }

结语

回到目录页:C#/.NET 知识汇总

希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。

相关推荐
林璟涵4 分钟前
Rust语言的系统运维
开发语言·后端·golang
海棠蚀omo6 分钟前
C++笔记-string(下)
开发语言·c++·笔记
褚眠莘15 分钟前
C#语言的加密货币
开发语言·后端·golang
monstercl43 分钟前
【Lua】pcall使用详解
开发语言·lua
东方苾梦1 小时前
SQL语言的计算机体系结构
开发语言·后端·golang
蹦蹦跳跳真可爱5891 小时前
Python----PaddlePaddle(深度学习框架PaddlePaddle,概述,安装,衍生工具)
开发语言·人工智能·python·paddlepaddle
一只专注api接口开发的技术猿1 小时前
京东API智能风控引擎:基于行为分析识别恶意爬虫与异常调用
大数据·开发语言·前端·爬虫
谬了个大也2 小时前
go --- go run main.go 和 go run .
开发语言·后端·golang
可可南木2 小时前
BT-Basic函数之首字母S
开发语言·测试工具·pcb工艺
涛涛讲AI2 小时前
Python urllib3 全面指南:从基础到实战应用
开发语言·python·urllib3