C#基础 - 委托(匿名函数、lambda、泛型Action/Func和注意点)
委托和事件都看了几遍了,感觉还是模模糊糊的,得写个文章总结一下委托和事件。弄明白委托和事件是对C#异步编程、同步、任务的理解的基础。
出现事件的地方,必然有委托出现;而委托则不一定会有事件出现。
1. 委托
委托在C#中定义为一种面向对象形式的方法寻址方案。简单来讲,就是定义一个类型,然后表示这个类型代表某一种方法。所以委托和class同级别,是一种类型,并且是引用类型。
委托可以实现将方法当做一个参数传递给另一个方法。
委托不关心方法叫什么,也不关心方法从哪来(归属于哪个类或者哪个对象),只关心方法需要***哪些参数,返回什么类型***。
记住 : 委托 == 引用类型 == delegate关键字+返回类型+参数
委托的定义形式:
delegate <返回类型> 委托名(参数列表);//参数列表代表任意个参数
委托可以定义在一个类的外部也可以定义在类的内部。
例子:
public delegate double CalculateDelegate(double x, double y);
上述委托声明了一个计算的规范,那么我们来为它赋值(使用了lambda表达式):
CalculateDelegate add= (x, y) => x + y;// 加
CalculateDelegate subtract = (x, y) => x - y;// 减
CalculateDelegate multiple = (x, y) => x * y; //乘
CalculateDelegate devide = (x, y) => x / y; //除
这儿说一下生产环境中委托实例化的命名规则是一般表示 —— 一个表示动作的名词!实例可以为null,使用时也需要做null判断。
调用:
double addResult= add(10, 10);
double subtractResult= subtract(19, 10);
double multipleResult= multiple(10, 5);
double devideResult = devide(10,5);
特别的,C#中委托支持多路广播,所以也可以使用+、-进行注册和删除。多路广播是指在事件和委托中有多个监听器或响应方法,当事件触发或者委托调用的时候,注册的方法组(即委托存储的方法)全部都会被调用。当使用这种方式对委托进行赋值的时候,委托将自动转为方法组(就像变成了委托数组CalculateAreaDelegate[],然而不是),简单理解就是 委托对象内部创建了一个列表,然后把赋值给它的方法都存进去了。
CalculateDelegate calculate = add;// calculate必须先赋值一个方法
calculate += subtract;// 增加
calculate += multiple; // 增加
calculate -= subtract; // 减去
当用calculate(x,y)
运行时,这儿calculate的运行结果只会返回最后一次注册的方法的执行结果,其他的方法执行了,但是方法的执行结果无法用变量接收到。
所以这里有一个很重要的实践,如果有需要把委托当做一个方法列表进行使用的时候,最好声明为void或者抛弃返回值的具体内容。
特别指出:多播委托执行顺序不确定,随着.net framework\core的版本不同,测试电脑的不同,各种执行顺序结果均有差异。所以不可编写依赖特定顺序调用方法的代码!!!
另:如果通过委托调用其中的一个方法抛出异常,整个迭代就会停止。
解决办法:使用Delegate类
Delegate类定义GetInvocationList()方法,返回一个Delegate数组。
Delegate[] dels = calculate.GetInvocationList();
foreach (Calculate itemDel in dels)
{
try
{
itemDel(10,2);
}
catch (Exception)
{
throw;
}
}
2. 匿名方法
可以方便快捷的使用委托。是用作委托参数的一段代码,可以方便快捷的使委托实例化。其底层编译还是会编译成一个方法。
不建议再使用匿名方法,C#3.0后使用lambda表达式替代匿名方法。
Calculate calculate = delegate (double x, double y)
{
return x + y;
};
注意:
- 匿名方法内部不能使用跳转语句。
- 匿名方法内部不能访问不安全的代码。也不能访问在匿名方法外部使用的ref /out参数。
3. Lambda表达式
语法:
委托类型 del = (参数x, 参数 y) =>
{
//todo
};
=>的左边定义参数,右边编写逻辑代码。
- 如果委托类型定义了一个参数,则可以免写参数类型,免写参数括号。
public delegate string AddTimeDelegate(string content);
AddTimeDelegate del = s =>
{
return s + DateTime.Now.ToString();
};
***另外***如果方法块只有一条语句时,可以免写return和方法块花括号(一起免写),编译器会自动生成一个隐式的return语句。
public delegate string AddTimeDelegate(string content);
AddTimeDelegate del = s => s + DateTime.Now.ToString();
- 如果委托类型定义了多个参数,需要括号括起来
public delegate string ConnectDelegate(string s1,string s2);
ConnectDelegate del2 = (string s1, string s2) =>
{
return s1 + s2;
};
Console.WriteLine(del2("hello ", "world!"));
lambda表达式可以访问表达式块外部的变量,称为闭包。访问外部变量的lambda表达式是
Lambda表达式的本质是“匿名方法”,即当编译我们的程序代码时,“编译器”会自动将“Lambda表达式”转换为“匿名方法”。
4. 泛型委托Func和Action
方法的返回类型和签名千千万万,无法对每个方法都去定义对应的委托,.net为了方便使用委托,定义了两个泛型委托。
1. Action
Action<int T,1in T2,…>:表示引用一个void返回类型的方法。至多可以传递16种不同类型的参数。其中,Action可调用没有参数的方法。
2. Func
Func<in T1,in T2,…,out TResult>允许调用带返回类型的方法。至多可以传递16种不同类型的参数和一个返回类型。返回类型放在<>最后一个。其中,Func调用一个带返回类型且无参数的方法。