24.C# Lambda表达式

时间:2023-03-08 16:47:18
24.C# Lambda表达式

1.Lambda表达式的含义

Lambda表达式是C#3.0引入的一种结构,使用它可以简化C#编程。

2.Lambda表达式与匿名方法

我们知道匿名方法可用于事件处理,如下delegate声明了一个匿名方法,它用于timer的Elapsed事件。

 System.Timers.Timer timer = new System.Timers.Timer();
timer.Elapsed+= delegate(object source,ElapsedEventArgs e){
Console.WriteLine("Event handler called after {0} miliseconds.", (source as System.Timers.Timer).Interval);
};

在我看来匿名函数的实质上就是用一个没有函数名称的方法创建一个委托,它是一个委托,因为它纯粹是为了委托而存在的。

如果不使用匿名方法,就需要创建匹配该事件的委托实例来订阅事件,比如

System.Timers.Timer timer = new System.Timers.Timer();
timer.Elapsed += new ElapsedEventHandler(TimerElapsed); public static void TimerElapsed(object source,ElapsedEventArgs e)
{
Console.WriteLine("Event handler called after {0} miliseconds.", (source as System.Timers.Timer).Interval);
}

也可以使用如下写法,编译器会自动为把TimerElapsed方法封装成对应的委托,但一般不这样写,这样会让人不好理解。

timer.Elapsed += TimerElapsed;

我们可以使用lambda表达式替代前面的匿名方法,编译器会用lambda表达式创建一个匿名方法,该匿名方法和之前的相同或者相似。可以说lambda表达式本质上是一个匿名方法,也就算是一个委托

System.Timers.Timer timer = new System.Timers.Timer();
timer.Elapsed += (source, e) => Console.WriteLine("Event handler called after {0} miliseconds.", (source as System.Timers.Timer).Interval);
timer.Start();

3.lambda表达式的构成

lambda表达式由3部分构成:

1)参数列表

2)=>运算符

3)表达式体

参数列表类似于方法参数,可以不定义参数类型,lambda表达式会使用类型推理功能根据实质传递的参数确定参数的类型,也可以显示定义参数类型,但是如果显示定义了参数类型,所有的参数就必须定义参数类型。

(int a, int b)=> a+b;

显式定义参数比较好理解,但是不够灵活,如下的定义可以使用long等其他类型参数,上面的表达式只能使用int。

(a,b)=>a+b;

参数列表有多个参数时用逗号分开,只有一个参数时可以不用括号,如

a=>a*a

也可以定义没有参数的lambda表达式

()=>Math.PI

=>运算符把参数列表和表达式分开

表达式体类似于方法体,lambda表达式表达式体可以包含多个语句,有返回值且包含多个语句的lambda表达式如果需要用return关键字指定返回值,单个语句可以不用return,比如

(a,b)=>{
//其他语句...
return a+b;
}

大多数情况下只有在语句较少的情况使用lambda表达式,语句比较多还是创建一个方法,方便重用。

 4.Lambda表达式和系统定义的委托Action、Action<>、Func<>

如前所述,我们指定lambda表达式实际上是一个委托,那么我们就可以用lambda表达式表示系统定义的如下委托,它们在System命名空间中定义

1)Action表示的lambda表达式不带参数,没有返回值;

Action的定义

namespace System
{
// 摘要:
// 封装一个方法,该方法不具有参数并且不返回值。
[TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089")]
public delegate void Action();
}

2)Action<>表示的lambda表达式至多带16个参数(至少带一个参数),没有返回值(基于4.0)

24.C# Lambda表达式

其中一个委托的定义

namespace System
{
// 摘要:
// 封装一个方法,该方法只有一个参数并且不返回值。
//
// 参数:
// obj:
// 此委托封装的方法的参数。
//
// 类型参数:
// T:
// 此委托封装的方法的参数类型。
public delegate void Action<in T>(T obj);
}

3)Func<>表示的lambda表达式至多带16个参数(可以不带参数),有返回值

其中一个委托的定义

namespace System
{
// 摘要:
// 封装一个具有一个参数并返回 TResult 参数指定的类型值的方法。
//
// 参数:
// arg:
// 此委托封装的方法的参数。
//
// 类型参数:
// T:
// 此委托封装的方法的参数类型。
//
// TResult:
// 此委托封装的方法的返回值类型。
//
// 返回结果:
// 此委托封装的方法的返回值。
[TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089")]
public delegate TResult Func<in T, out TResult>(T arg);
}

不带参数的委托

namespace System
{
// 摘要:
// 封装一个不具有参数但却返回 TResult 参数指定的类型值的方法。
//
// 类型参数:
// TResult:
// 此委托封装的方法的返回值类型。
//
// 返回结果:
// 此委托封装的方法的返回值。
[TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089")]
public delegate TResult Func<out TResult>();
}

Func<>泛型中,返回值总是在最后一个参数,比如

Func<int,int,int> a = (b,c)=>b+c;

5.Lambda表达式和集合

学习了Func<>就可以理解System.Linq命名空间为数组系列提供的一些方法了,比如累加器Aggregate()方法。Aggregate()方法有三个重载版本,如下

        public static TSource Aggregate<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, TSource> func);

        public static TAccumulate Aggregate<TSource, TAccumulate>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func);

        public static TResult Aggregate<TSource, TAccumulate, TResult>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func, Func<TAccumulate, TResult> resultSelector);

这几个重载版本都是对数组的每个元素应用累加器函数,每个版本略有不同:

第一个版本比较简单,它把数组的第一第二个元素用作累计器函数的参数,计算得出的结果用作参数参与第二轮计算,和数组第三个元素一起再次传入累加器函数,这样依次叠加,直到最后一个元素。

第二个版本返回值类型可以和数组类型不一样,它输入一个初始值,依次和每一个数组元素累加起来。

第三个版本在第二个版本的基础上,允许我们在使用累计器函数得到的结果上用指定的函数选择结果

如下是一个例子

class Program
{
static void Main(string[] args)
{ string[] curries = { "pathia", "jalfreze", "korma" }; //累加字符串数组
Console.WriteLine(curries.Aggregate((a, b) => a + " " + b)); //计算字符串数组总长度
Console.WriteLine(curries.Aggregate(, (a, b) => a + b.Length)); //a=>a表示参数和返回类型同样的匿名函数,在此表示返回用累加器叠加后得到的字符串本身
Console.WriteLine(curries.Aggregate("Some curries:", (a, b) => a + " " + b, a => a)); //a=>a表示参数和返回类型同样的匿名函数,在此表示返回用累加器叠加后得到的的字符串本身的长度
Console.WriteLine(curries.Aggregate("Some curries:", (a, b) => a + " " + b, a => a.Length)); Console.ReadLine();
}
}

注上面的例子中没有明确指定泛型类型,比如

Console.WriteLine(curries.Aggregate<string>((a, b) => a + " " + b));

Console.WriteLine(curries.Aggregate<string,int>(, (a, b) => a + b.Length));

因为实参的类型可以使用类型推理得来,所有以上等价于

Console.WriteLine(curries.Aggregate((a, b) => a + " " + b));

            //计算字符串数组总长度
Console.WriteLine(curries.Aggregate(, (a, b) => a + b.Length));

当不能使用类型推理得到参数的类型,编译器会要求显式标明参数类型。