目录
// 引用声明:本文文字内容源自 Solis, D.M., Illustrated C# 2012(Fourth Edition). 2013: 人民邮电出版社.
// 本博文仅为学习记录使用,不用于任何商业用途,引用请尊重作者版权!
一、一个简单的委托实例
//定义一个没有返回值和参数的委托类型
delegate void PrintFunction();
class Test
{
public void Print1()
{
Console.WriteLine("Print1 -- instance");
}
public static void Print2()
{
Console.WriteLine("Print2 -- static");
}
}
class Program
{
static void Main()
{
Test t = new Test(); //创建一个测试类实例
PrintFunction pf; //创建一个空委托
pf = t.Print1; //实例化并初始化该委托(快捷写法)
/* 为委托增加3个另外的方法 */
pf += Test.Print2; //可以添加静态方法
pf += t.Print1;
pf += Test.Print2;
/* 现在委托有4个方法 */
if (null != pf) //确认委托有方法
pf(); //调用方法
else
Console.WriteLine("Delegate is empty");
Console.ReadKey();
}
}
-------------------------------------------
Print1 -- instance
Print2 -- static
Print1 -- instance
Print2 -- static
-------------------------------------------
二、匿名方法
2.1 匿名方法
匿名方法是在初始化委托时内联(inline)声明的方法。
2.2 能使用匿名方法的地方
1. 声明委托变量时作为初始化表达式;
2. 组合委托时在赋值语句的右边;
3. 为委托增加事件时在赋值语句的右边;
2.3 语法
匿名方法的表达式包含如下组成部分:
// delegate类型关键字
// 参数列表,如果语句块没有使用任何参数则可以省略
// 语句块,它包含了匿名方法的代码
delegate (Parameters) {ImplementationCode}
2.3.1 返回类型
匿名方法不会显式声明返回值。然而,实现代码本身的行为里必须返回一个与委托声明中返回类型相同的值来匹配委托的返回类型。
//委托声明中的返回类型是int
delegate int OtherDel(int Inparam);
static void Main(string[] args)
{
OtherDel del = delegate (int x)
{
return x + 10; //代码本身的路径就要返回int 类型
};
}
2.3.2 参数
除了数组参数,匿名方法的参数列表必须在如下3方面与委托匹配:
(1)参数数量;(2)参数类型及位置;(3)修饰符
另外,我们可以通过使用圆括号为空或者省略圆括号来简化匿名方法的参数列表,但必须同时满足以下两个条件:
(1)委托参数列表不包含任何out参数;(2)匿名方法体内不使用任何参数。
delegate void SomeDel(int x); //声明委托类型
SomeDel SDel = delegate //使用委托的参数列表
{
PrintMessage();
CleanUP;
}
2.3.3 params参数
如果委托声明的参数列表包含了params参数,那么匿名方法的参数列表将忽略params关键字。例如,下列代码:
delegate void SomeDel(int x, params int[] y); //委托类型声明中使用了params关键字
SomeDel SDel = delegate(int x, int[] y) //在匹配的匿名方法中省略关键字
{
}
2.4 变量和参数的作用域
参数以及声明在匿名方法内部的局部变量的作用域限制在实现方法的主体之内,离开该作用域再使用局部变量就会报错。
2.4.1 外部变量
与委托的具名方法不同,匿名方法可以访问它们外围作用于的局部变量和环境。
delegate void MyDel(int x); //委托类型声明
public static int x = 5; //变量x定义在匿名方法的作用域前面
MyDel mDel = delegate
{
Console.WriteLine("{0}", x); //变量x可以在匿名方法作用域内使用
};
2.4.2 捕获变量生命周期的拓展
只要捕获方法还是委托的一部分,即使变量已经离开了作用域,捕获的外部变量也会一直有效。
delegate void MyDel();
static void Main()
{
MyDel mDel;
//变量x的块作用域
{
int x = 5; //变量x定义在块内部,在匿名方法之外
mDel = delegate
{
Console.WriteLine("Value of x : {0}", x); //变量x被匿名方法捕获
};
}
Console.WriteLine("Value of x : {0}",x); //error,变量x离开了作用域并会导致编译错误
if (null != mDel)
mDel(); //而x可以在这里使用,在匿名方法的内部
}
三、Lambda表达式
上面的匿名方法是C#2.0里面的,我们可以看到语法有点复杂,如果早点有lambda表达式,就不会有匿名方法啥事儿了。
3.1 基础
我们很容易的通过下面的步骤把匿名方法转换成Lambda表达式:
1. 删除delegate关键字;
2. 在参数列表和匿名方法主体之间放Lambda运算符 => ,读作“goes to”;
3. 编译器还可以从委托的声明中知道委托参数的类型,因此Lambda表达式允许我们省略类型参数,如le2中赋值代码所示:(1)带有类型的参数列表称为显式类型(2)省略类型的参数列表称为隐式类型;
4. 如果只有一个隐式类型参数,我们可以省略周围的圆括号,如le3所示;
5. Lambda表达式允许表达式的主体是语句块或表达式。如果语句仅包含了一个语句且该语句是返回语句,我们可以将语句块替换为return关键字后的表达式,如le4的赋值代码所示;
MyDel del = delegate(int x) { return x + 1; }; //匿名方法
MyDel le1 = (int x) => { return x + 1; }; //lambda表达式
MyDel le2 = (x) => { return x + 1; }; //lambda表达式
MyDel le3 = x => { return x + 1; }; //lambda表达式
MyDel le4 = x => x + 1; //lambda表达式
3.2 巩固的例子
delegate double MyDel(int par);
class Program
{
static void Main()
{
MyDel del = delegate (int x) { return x + 1; }; //匿名方法
MyDel le1 = (int x) => { return x + 1; }; //Lambda表达式
MyDel le2 = (x) => { return x + 1; };
MyDel le3 = x => { return x + 1; };
MyDel le4 = x => x + 1;
Console.WriteLine("{0}", del(12));
Console.WriteLine("{0}", le1(12));
Console.WriteLine("{0}", le2(12));
Console.WriteLine("{0}", le3(12));
Console.WriteLine("{0}", le4(12));
}
}
/* 程序运行结果
---------------------------------------
13
13
13
13
13
---------------------------------------
*/
3.3 参数列表要点
1. Lambda表达式参数列表中的参数必须在参数数量、类型和位置上与委托相匹配;
2. 表达式的参数列表中的参数不一定需要包含类型(隐式类型),除非委托有ref或out参数——此时必须注明类型(显式类型);
3. 如果只有一个参数,并且是隐式类型的,周围的圆括号可以被省略,否则必须有括号;
4. 如果没有参数,必须使用一组空的括号;
5. Lambda表达式的语法为: