委托的定义
委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。
委托的目的
提供了对同类方法执行的动态调整的能力,使底层模块只依赖委托,但不依赖具体方法。就像面向接口编程一样,模块之间值依赖接口,但是不依赖具体的实现。
委托的实现
委托是一个引用类型,所以它具有引用类型所具有的通性。它保存的不是实际值,而是保存对存储在托管堆(managed heap)中的对象的引用。那它保存的是对什么的引用呢?委托保存的是对函数(function)的引用。.NET中的委托是类型安全的,委托会检测它所保存的函数引用是否和声明的委托匹配。当我们用delegate关键字声明委托时,编译器自动为我们生成类。类的名字即为委托变量名,并且继承自System.MulticastDelegate,该类包含一个构造器,还包含三个方法,分别是BeginInvoke、EndInvoke、Invoke。代码实现:
public delegate void GreetDelegate();
public class MyDemo
{
public void MeetPeople()
{
//直接调用方法
Program.GreetByEN();
}
public void MeetPeople(GreetDelegate greet)
{
//通过委托调用方法
greet();
}
}
class Program
{
static void Main(string[] args)
{
MyDemo demo = new MyDemo();
demo.MeetPeople();
demo.MeetPeople(GreetByCN);
demo.MeetPeople(GreetByEN);
//初始化一个委托
GreetDelegate greetDelegate1 = new GreetDelegate(GreetByCN);
GreetDelegate greetDelegate2 = GreetByCN;
//设置委托链:给此委托变量再绑定一个方法
greetDelegate1 += GreetByEN;
greetDelegate1();//先后调用:GreetByCN和GreetByEN
//设置委托链:给此委托变量移除一个方法
greetDelegate1 -= GreetByEN;
greetDelegate1();//只调用:GreetByCN
Action<string> action1 = delegate (string str)
{
Console.WriteLine(str);
};
Action<string> action2 = (str)=>
{
Console.WriteLine(str);
};
Console.ReadKey();
}
public static void GreetByEN()
{
Console.WriteLine("Hello");
}
public static void GreetByCN()
{
Console.WriteLine(@"Ni hao");
}
}
委托的定义:
public delegate void GreetDelegate();
定义委托和方法的签名对比,除了加入了delegate关键字以外,其余的完全一样。反编译看元数据:
创建委托变量并初始化,下面两种方式是一样的:
GreetDelegate greetDelegate1 = new GreetDelegate(GreetByCN);
GreetDelegate greetDelegate2 = GreetByCN;
给委托变量设置参数(追加引用函数):
//设置委托链:给此委托变量再绑定一个方法
greetDelegate1 += GreetByEN;
greetDelegate1();//先后调用:GreetByCN和GreetByEN
//设置委托链:给此委托变量移除一个方法
greetDelegate1 -= GreetByEN;
greetDelegate1();//只调用:GreetByCN
深度理解委托是类型安全的
我们分析这个代码:
string name;
在这里,string是变量的类型,name是具体的变量,当我们赋给name字符串“nestor”时,它就代表“nestor”这个值;当我们赋给它“liu”时,它又代表着“liu”这个值。我们再分析这个代码:
bool isActive;
在这里,bool是变量的类型,isActive是具体的变量,当我们赋给isActive等于true时,它就代表true这个值;当我们赋给它false时,它又代表着false这个值。但是我们不能给isActive赋值成“nestor”或者“liu”。因为类型不匹配。我们最后分析这个代码:
GreetDelegate greet;
在这里,GreetDelegate是变量的类型,greet是具体的变量,当我们赋给greet等于GreetByEN时,它就代表GreetByEN这个值;当我们赋给它GreetByCN时,它又代表着GreetByCN这个值。但是我们不能给greet赋值成其他种类的方法。因为类型不匹配。方法的种类是根据它的参数数量、参数类型和返回类型决定的。所以我们说委托是类型安全的。另外,委托与string相同,string是一个类型,那么委托也是一个类型,或者叫类(Class)。但是委托的声明方式和类却完全不同,这是怎么一回事?实际上,委托在编译的时候确实会编译成类。因为Delegate是一个类,所以在任何可以声明类的地方都可以声明委托。
我们现在对委托做一个总结:
1.委托是一个类。
2.它定义了方法的类型。
3.使得可以将方法当作另一个方法的参数来进行传递。
4.底层模块只依赖委托,但不依赖具体方法。
5.程序具有更好的可扩展性。
6.可以将多个方法赋给同一个委托,或者叫将多个方法绑定到同一个委托,当调用这个委托的时候,将依次调用其所绑定的方法。
匿名委托:Action,Func和Predicate。
1.Action是无返回值的泛型匿名委托。Action<int,string>表示有传入参数int,string无返回值的委托。
2.Func是有返回值的泛型委托。Func<int>表示无参,返回值为int的委托,Func<object,string,int> 表示传入参数为object, string 返回值为int的委托。
3.Predicate的使用:等同于Func<T,bool>。表示定义一组条件并确定指定对象是否符合这些条件的方法。此委托由Array和List类的几种方法使用,用于在集合中搜索元素。Predicate只能接受一个传入参数,返回值为bool类型。
匿名函数:与普通函数的区别就是将函数名换成了delegate,而且匿名函数肯定要保存到委托里。
Action<string> action = delegate (string str)
{
Console.WriteLine(str);
};
lambda表达式:更简捷的写法,因为匿名函数肯定要放到委托里,而且参数类型已确定好。
Action<string> action2 = (str)=>
{
Console.WriteLine(str);
};
.NET Framework中委托的应用
Action,Func,Predicate,Comparison,Coverter。
Comparison和Coverter定义如下:
public delegate int Comparison<in T>(T x, T y);
public delegate TOutput Converter<in TInput, out TOutput>(TInput input);
委托的缺点
当用委托定义一个类的字段时,它的封装性和安全性不好。假设我们把这个委托字段设置成public,在客户端可以对它进行随意的赋值和调用等操作,严重破坏对象的封装性和安全性。设置成private会怎样?结果就是:这简直就是在搞笑。因为声明委托的目的就是为了把它暴露在类的客户端进行方法的注册,你把它声明为private了,客户端对它根本就不可见,那它还有什么用?
最后,第一个方法注册用“=”,是赋值语法,因为要进行实例化,第二个方法注册则用的是“+=”。但是,不管是赋值还是注册,都是将方法绑定到委托上,除了调用时先后顺序不同,再没有任何的分别,这样不是让人觉得很别扭么?
如果字段不是一个委托类型,而是一个string类型,你会怎么做?答案是使用属性对字段进行封装。所以这个场景下委托不适合定义一个类的字段,于是,Event出场了,它封装了委托类型的变量,使得:在类的内部,不管你声明它是public还是protected,它总是private的。在类的外部,注册“+=”和注销“-=”的访问限定符与你在声明事件时使用的访问符相同。关于事件的具体细节,请听下回分解。