委托发展史(三)

时间:2021-08-14 19:50:53

通过C#2极大的简化了委托的使用。如果仅仅是为了简化事件的订阅以及增强可读性,这些技术确实已经足够了。

但是,C#2中的委托仍然过于臃肿:一页充满匿名方法的代码,读起来真让人难受,你也肯定不愿意经常在一个语句中放入多个匿名方法吧。

C#3可以说是一个工业革命。

*作为委托的Lambda表达式

从许多方面Lambda表达式都可以看做是C#2的匿名方法的一种演变。

匿名方法能做到的几乎一切事情都可以用Lambda表达式来完成,另外,几乎所有情况下,Lambda表达式都更易读,更紧凑。

从最显而易见的方面看,两者并无多大区别--只是Lambda支持许多简化语法使他们在常规条件下显得更简练。

与匿名方法相似,Lambda表达式有特殊的转换规则:表达式的类型本身并非委托类型,但它可以通过多种方式隐式或显式转换成一个委托实例。

匿名函数这个术语同时涵盖了匿名方和Lambda表达式--,在很多情况下,两者可以使用相同的转换规则。

慢慢来看这一场工业革命吧。。。

*Func<...>委托类型简介

在.NET3.5的System命名空间中,有5个泛型Func委托类型,

Func并无特别之处——只是他提供了一些好用的预定义泛型类型,在很多情况下能帮助我们处理问题。

每个委托签名都获取0~4个参数,其类型用类型参数来指定。最后一个类型参数用作每种情况下的返回类型。

通俗讲就是这个Func是一个有返回值委托类型。

看一下.NET3.5所有Func委托的签名:

TResult Func<TResult> ();
TResult Func<T,TResult> (T arg);
TResult Func<T1,T2,TResult> (T1 arg1,T2 arg2);
TResult Func<T1,T2,T3,TResult> (T1 arg1,T2 arg2,T3 arg3);
TResult Func<T1,T2,T3,T4,TResult> (T1 arg1,T2 arg2, T3 arg3, T4 arg4);

例如,Func<string,double,int>  等价于以下形式的委托类型。

public delegate int TestDelegate(string arg1, double arg2);

当你想返回void时,也就是无返回值,可使用Action<...>系列委托,其功能相似。

Action在.Net2.0中就有了,但其他都是.NET 3.5新增的。如果4个参数还嫌不够,.NET 4将Action与Func家族扩展为拥有16个参数。

因此Func<T1,..., T16 , TResult >拥有17个参数类型。

例如,我们需要获取一个stirng参数,并返回一个int,所以我们将使用Func<string,int>。

*转换到Lambda表达式

Func<string, int> returnLength;
returnLength = delegate (string text) { return text.Length; };
Console.WriteLine(returnLength("Hellow"));

最后会输出"5",预料之中。

注意上面的代码,returnLength的声明与赋值是分开的,否则一行可能放不下——除此之外,这样还有利于对代码的理解。所以,我们将它转换成Lambda表达式

Lambda表达式最冗长的形式是:

(显式类型的参数列表) => { 语句 }

=>这个是C#3新增的,他告诉编译器我们正在使用一个Lambda表达式。Lambda表达式大多数时候都和一个返回非void的委托类型配合使用——如果不反悔一个结果,语法就不像现在这样一目了然。

这个版本包含显式参数,并将语句放到大括号中,他看起来和匿名方法非常相似。

Func<string, int> returnLength;
returnLength = (string text) => { return text.Length; };
Console.WriteLine(returnLength("Hellow"));

在阅读Lambda表达式时,可以将=>部分看成"goes to"。

匿名方法中控制返回语句的规则同样适用与Lambda表达式:不能从Lambda表达式返回void类型;

如果有一个非void的返回类型,那么每个代码路径都必须返回一个兼容的值。

到目前为止,使用Lambda表达式并没有节省多大空间,或使代码变得容易阅读。

*用单一表达式作为主体

我们目前使用一个完整的代码块来返回值,这样可以灵活地处理多种情况——可以在代码块中放入多个语句,可以执行循环,可以从代码块中不同位置返回。。。等等

这和匿名方法是一样的。

然而,大多数时候,都可以用一个表达式来表示整个主体,该表达式是Lambda结果。(意思就是,一条语句就可以解决的事)

这些情况下,可以指定那个表达式,不用大括号;不使用return语句,也不添加分号,格式随即变成:

(显式类型的参数列表) => 表达式

在我们的例子中,就变成了——

(string text) => text.Length

现在已经开始变得简单了,接着来考虑一下参数类型。编译器已经知道Func<string,int>的实例获取单个字符串,所以只需命名那个参数就可以了。

感觉还是得分两行来声明跟赋值啊。。。

*隐式类型的参数列表

编译器大多数时候都能猜出参数类型,不需要你显示声明他们。这些情况下,

还可以更加简便些。

(隐式类型的参数列表) => 表达式

 嗯,更加简便了,Lambda表达式也变成了这样:

(text) => text.Length

隐式类型的参数列表就是一个以逗号分隔的名称列表,没有类型。但是隐式和显式类型的参数不能混合匹配——要么整个列表都是显式类型,要么都是隐式类型。

如果存在out 或 ref参数,那么就只能是显式类型了。

上面的Lambda表达式已经相当简短了,可以继续简化的地方不多了。

哎~这个圆括号看起来有点多余啊。除去它!

*单一参数的快捷语法

如果Lambda表达式只需要一个参数,而且那个参数可以隐式指定类型,C#3就允许省略圆括号。这种Lambda表达式是:

参数名 => 表达式

因此我们的Lambda表达式最终形式是:

text => text.Length

这样的话如果一小段代码中含有多个Lambda,那么拿掉参数列表的圆括号之后,对于可读性来说是增强不少的。

还有如果愿意,可以用圆括号将整个Lambda表达式括起来。

在大多数情况下这种形式都是十分易读的,例如之前的例子写出来就是这样:

Func<string, int> returnLength;
returnLength = text => text.Length;

Console.WriteLine(returnLength("Hellow"));

可能刚开始读起来有点"别扭",不过很快就习惯啦~

当你习惯了Lambda表达式之后,你一定会感慨他们是多么的简洁,很难想象还可以使用更短,更清晰的方式老创建委托实例。

*Lambda语法简写总结

委托发展史(三)