委托,匿名函数和lambda表达式
很早之前就接触到了委托,但是一直对他用的不是太多,主要是本人是菜鸟,能写的比较高级的代码确实不多,但是最近在看MSDN微软的类库的时候,发现了微软的类库好多都用到了委托,于是决定好好的研究研究,加深一下自己对他的印象,顺便把自己的感悟和想法写出来,以便自己能有更深的理解,由于本人水平有限,也欢迎大家多多探讨,有问题大家一起解决一起进步。
MSDN上面对委托的定义是:delegate 是表示对具有特定参数列表和返回类型的方法的引用的类型。在我看来委托的特性主要在:
1.把一个方法当做一个参数传递给另一个函数。这样可以做到方法的扩展,比如我们在比较两个同种类型的数据(两个相同类的对象)时,我们只需要向比较方法中传递比较的对象和比较的规则,这个规则就可以是一个委托的形式去传入,我们不清楚不同类型的对象的比较的方法,我们在比较的时间只需要将对象和对应的比较规则传进去就好。看下面代码:
namespace DelegateTest { internal class Program { private static void Main(string[] args) { Person p1 = new Person {Name = "p1", Age = 19}; Person p2 = new Person {Name = "p2", Age = 22}; //传入比较的对象和具体的比较规则 ComparePerson(p1, p2, ComparePersonRule); } //定义委托 public delegate bool CompareRule(int x, int y); //这个方法就是委托指向的具体的比较的方法 public static bool ComparePersonRule(int x, int y) { return x > y; } //在比较peson类型的数据时,不需要知道具体的比较规则,只需要传入一个委托,具体的比较由委托 compareRule指向的具体方法来实现 public static void ComparePerson(Person p1, Person p2, CompareRule compareRule) { if (compareRule(p1.Age, p2.Age)) { Console.WriteLine(p1.Name + "比" + p2.Name + "年纪大!"); Console.ReadKey(); } else { Console.WriteLine(p1.Name + "比" + p2.Name + "年纪小!"); Console.ReadKey(); } } public class Person { public int Age { get; set; } public string Name { get; set; } } } }
再换比较对象的类型时,只要换不同的委托实体就行,这样可以极大的扩展代码的可用性。
2.委托用于回调函数,回调函数就是把一个方法的传给另外一个方法去执行。在C#有很多回调函数,比如异步操作的时候。这里先举个例子:
namespace DelegateCallbackTest { /// <summary> /// 委托 /// </summary> /// <param name="s1"></param> /// <param name="s2"></param> /// <returns></returns> public delegate string ProcessDelegate(string s1, string s2); class Program { static void Main(string[] args) { /* 调用方法 */ Test t = new Test(); string r1 = t.Process("Text1", "Text2", new ProcessDelegate(t.Process1)); Console.WriteLine(r1); Console.ReadKey(); } } public class Test { public string Process(string s1, string s2, ProcessDelegate process) { return process(s1, s2); } public string Process1(string s1, string s2) { return s2 + s1; } } }
这里委托的方法最后在Test的Process方法里面执行,这就是一种最简单的回调。在C#里面有好多委托回调的例子,这里就不再说了。
3.多播委托,事件的实现就是通过多播委托来实现的,一个+=和一个—=,形成一个委托链,一个一个的执行委托指向的方法。
说到委托自然而然离不开匿名函数,我感觉匿名函数就是为了方便写委托而产生的,或许是我对它的理解还不到位,匿名方法是C#2.0引入的一个新特性,它允许开发者内联(inline)声明自己的函数代码而无须使用委托函数(delegate function。匿名方法通常在1. 需要一个临时方法,该方法使用次数极少;2. 这个方法的代码很短,甚至可能比方法声明都短的情况下使用。你可以把C# 匿名方法想象为一个实现与委托进行关联这项功能的便捷途径。如果同时看一下匿名方法实现和命名方法实现所取得IL结果,你会发现这两者之间的差别非常小。当编译器碰到匿名方法的时候,它会在类里面创建一个命名方法,并将它与委托进行关联。所以匿名方法在运行期间与命名方法的性能非常类似——性能的增加体现在开发人员的生产效率上,而不是运行期间的执行上。在 C# 1.0 中,您通过使用在代码中其他位置定义的方法显式初始化委托来创建委托的实例。 C# 2.0 引入了匿名方法的概念,作为一种编写可在委托调用中执行的未命名内联语句块的方式。 C# 3.0 引入了 Lambda 表达式,这种表达式与匿名方法的概念类似,但更具表现力并且更简练。 这两个功能统称为“匿名函数”。 通常,针对 .NET Framework 版本 3.5 及更高版本的应用程序应使用 Lambda 表达式。
下面的示例演示了从 C# 1.0 到 C# 3.0 委托创建过程的发展:
class Test { delegate void TestDelegate(string s); static void M(string s) { Console.WriteLine(s); } static void Main(string[] args) { // Original delegate syntax required // initialization with a named method. TestDelegate testDelA = new TestDelegate(M); // C# 2.0: A delegate can be initialized with // inline code, called an "anonymous method." This // method takes a string as an input parameter. TestDelegate testDelB = delegate(string s) { Console.WriteLine(s); }; // C# 3.0. A delegate can be initialized with // a lambda expression. The lambda also takes a string // as an input parameter (x). The type of x is inferred by the compiler. TestDelegate testDelC = (x) => { Console.WriteLine(x); }; // Invoke the delegates. testDelA("Hello. My name is M and I write lines."); testDelB("That's nothing. I'm anonymous and "); testDelC("I'm a famous author."); // Keep console window open in debug mode. Console.WriteLine("Press any key to exit."); Console.ReadKey(); } } /* Output: Hello. My name is M and I write lines. That's nothing. I'm anonymous and I'm a famous author. Press any key to exit. */
下面是在MSDN上的摘要:
Lambda 表达式是一种可用于创建委托或表达式目录树类型的匿名函数。通过使用 lambda 表达式,可以写入可作为参数传递或作为函数调用值返回的本地函数。 Lambda 表达式对于编写 LINQ 查询表达式特别有用。 若要创建 Lambda 表达式,需要在 Lambda 运算符 => 左侧指定输入参数(如果有),然后在另一侧输入表达式或语句块。 例如,lambda 表达式 x => x * x 指定名为 x 的参数并返回 x 的平方值。
=> 运算符具有与赋值运算符 (=) 相同的优先级并且是右结合运算(参见“运算符”文章的“结合性”部分)。
Lambda 在基于方法的 LINQ 查询中用作标准查询运算符方法(如 Where)的参数。
使用基于方法的语法在 Enumerable 类中调用 Where 方法时(如在 LINQ to Objects 和 LINQ to XML 中一样),参数是委托类型 System.Func<T, TResult>。 使用 Lambda 表达式创建该委托最为方便。 例如,当你在 System.Linq.Queryable 类中调用相同的方法时(如在 LINQ to SQL 中一样),参数类型为 System.Linq.Expressions.Expression<Func>,其中 Func 是最多具有十六个输入参数的任何一个 Func 委托。 同样,Lambda 表达式只是一种非常简洁的构造该表达式目录树的方式。 尽管事实上通过 Lambda 创建的对象具有不同的类型,但 Lambda 使得 Where 调用看起来类似。
在上一个示例中,请注意委托签名具有一个 int 类型的隐式类型输入参数,并返回 int。 可以将 Lambda 表达式转换为该类型的委托,因为该表达式也具有一个输入参数 (x),以及一个编译器可隐式转换为 int 类型的返回值。(以下几节中将对类型推理进行详细讨论。)使用输入参数 5 调用委托时,它将返回结果 25。
适用于匿名方法的所有限制也适用于 Lambda 表达式。
可见lambda是一种更加方便的匿名函数,可以说完全是为了委托而产生的。以上只为自己个人愚见,还望能和大家多多交流。