看C#高级编程第八版看到委托,有人说它和Java中的代理有些相似,但我认为这是一个C#与其他编程语言不同的地方,这也应该很重要,不然书上也不会大篇幅的讲委托的概念和应用。我在网上查阅了关于委托的资料,发现它们就像是一道槛儿,过了这个槛的人,觉得真是太容易了,而没有过去的人每次见到委托和事件就觉得心里憋得慌,混身不自在。
那初次看见“委托”这个词,我脑海中出现的第一个疑问就是:
1.什么是委托?
我看完整这个章节,依然是一知半解的,功夫不负有心人,通过强大的网路,我寻找到了答案,那到底什么是委托呢?
委托就是可以把方法作为参数传递,也就是说通过委托可以像操作变量一样的操作方法参数。看到这里我发现这并不是C#首创的这个概念,因为在C++中就有函数指针的概念和应用,我理解这其实就是微软把C++中的函数指针作为原型而衍生出来的,经过封装的这么一个特殊概念,其实现在我看来也不是很特别吗,毕竟在C++中曾经学过呀。
现在我已经知道了委托的概念了,那么我第二个问题又来了。
2.这个委托怎么用呢?
我觉得怎么用,还是要拿Demo来进行讲解会比较方便理解和对比。下面是我参看网络上的大神们所用到代码,我们不讨论所给Demo的代码是否有实际意义,只是为了让读者能够更加方便的理解什么叫委托。代码如下:
a.现在要模仿人打招呼。那么我们需要知道他的名字方法如下:
public static void ChineseGreeting(string name){ Console.WriteLine("早上好!"+name); } static void Main(string[] args) { ChineseGreeting("李雷"); Console.ReadKey(); }
b.要是现在要给一个老外(不懂中文的)打招呼,那要这怎么办呢?是不是应该知道给老外说英文呢?单独创建一个方法呢?不过在创建方法之前,我们需要先用一个枚举类型去判读是外国人还是中国人。
代码如下:
public enum Language{ Chinese,English } public void EnglishGreeting(string name){ Console.WriteLine("Morning !"+name); } public GreatPeople(string name,Language language){ switch(language) { case language.English:EnglishGreeting(name);break; case language.Chinese:ChineseGreeting(name);break; } }
只要有些编程经验的人就知道,上面的解决方案不好,如果下次有日本人,有韩国人,非洲人等等,那么我们是不是就又要去添加枚举元素,添加问候方法!上面的方法不利于扩展。所以我们就想有没有什么可以不用这么麻烦而且能很好的扩展呢?我现在分析上面的解决方案是如何做到的。
首先添加一个对应语言的问候的方法是不可少的,枚举元素也是必须的,枚举元素是为了判断调用哪个语言的问候方法去问候的标志,那么既然我们前面提到了方法可以做参数那为什么不直接传方法名称呢?下面就是利用代理实现上面方案的代码如下:
public delegate void GreetingDelegate(string name); class Program { public static void EnglishGreeting(string name) { Console.WriteLine("Morning!"+name); } public static void ChineseGreeting(string name) { Console.WriteLine("早上好!" + name); } //这是一个 public static void GreetingPeople(string name, GreetingDelegate greetingmethod) { greetingmethod(name);// } static void Main(string[] args) { GreetingPeople("Mike", EnglishGreeting); GreetingPeople("李雷", ChineseGreeting); Console.ReadKey(); } }
看上面的代码,是不是感觉舒服多了我们不用使用枚举了,也不用进行选择(switch)了。
请注意:public static void GreetingPeople(string name, GreetingDelegate greetingmethod)这个方法中使用了一个委托类型的参数传递使用的问候方法。是不是感觉很神奇。
看到上面的Demo你是不是对委托有些感觉了呢?现在我的问题又来了。
3.方法是如何绑定到委托的?
这个问题我也需要借用上面的Demo进行修改一下,以便于显示效果。Demo代码如下:
public delegate void GreetingDelegate(string name); class Program { public static void EnglishGreeting(string name) { Console.WriteLine("Morning,"+name); } public static void ChineseGreeting(string name) { Console.WriteLine("早上好!" + name); } public static void GreetingPeople(string name, GreetingDelegate greetingmethod) { Console.WriteLine("问候开始..."); greetingmethod(name); Console.WriteLine("问候结束!"); } static void Main(string[] args) { GreetingDelegate delegate1;//委托实例 delegate1 = EnglishGreeting; delegate1 += ChineseGreeting; GreetingPeople("Mike", EnglishGreeting); GreetingPeople("李雷", ChineseGreeting); GreetingPeople("John", delegate1); Console.ReadKey(); } }
结果如下:
多个方法可以被绑定到一个委托实例中去。
注释:delegate1 = EnglishGreeting;这个操作符"="是初始化委托实例,那么"+="是绑定语法。那么到底
初始化和绑定还可以这样做:
GreetingDelegate delegate1; delegate1 = EnglishGreeting; delegate1 += ChineseGreeting; GreetingPeople("John", delegate1); GreetingDelegate delegate2 = new GreetingDelegate(EnglishGreeting); delegate2 += ChineseGreeting; GreetingPeople("LiLy", delegate2); Console.ReadKey();
这样的看,委托是不是和类非常的相似!你先下面这段代码和信息你会发现越发的像。
GreetingDelegate delegate3 = new GreetingDelegate();//报错,不含有0个参数的构造函数 delegate3 += EnglishGreeting; Console.ReadKey();
说明编译器在编译的时候会把委托当做类进行编译。这一点也是函数指针比不了的。
总结:
GreetingDelegate
1.委托定义
委托的声明原型是
delegate <函数返回类型> <委托名> (<函数参数>)
例子:public delegate void GreetingDelegate(string name);//定义了一个委托它可以注册返回void类型且有一个int作为参数的函数这样就定义了一个委托,但是委托在.net内相当于声明了一个类,类如果不实例化为对象,很多功能是没有办法使用的,委托也是如此.
2.委托实例化
委托实例化的原型是
<委托类型> <实例化名>=new <委托类型>(<注册函数>)
例子:CheckDelegate _checkDelegate=new CheckDelegate(CheckMod);//用函数CheckMod实例化上面的CheckDelegate 委托为_checkDelegate
在.net 2.0开始可以直接用匹配的函数实例化委托:
<委托类型> <实例化名>=<注册函数>
例子:CheckDelegate _checkDelegate=CheckMod;//用函数CheckMod实例化上面的CheckDelegate 委托为_checkDelegate现在我们就可以像使用函数一样来使用委托了,在上面的例子中现在执行_checkDelegate()就等同于执行CheckMod(),最关键的是现在函数CheckMod相当于放在了变量当中,它可以传递给其它的CheckDelegate引用对象,而且可以作为函数参数传递到其他函数内,也可以作为函数的返回类型
3.用匿名函数初始化委托
上面为了初始化委托要定义一个函数是不是感觉有点麻烦,另外被赋予委托的函数一般都是通过委托实例来调用,很少会直接调用函数本身。
在.net 2.0的时候考虑到这种情况,于是匿名函数就诞生了,由于匿名函数没有名字所以必须要用一个委托实例来引用它,定义匿名函数就是为了初始化委托
匿名函数初始化委托的原型:
<委托类型> <实例化名>=new <委托类型>(delegate(<函数参数>){函数体});
当然在.net 2.0后可以用:
<委托类型> <实例化名>=delegate(<函数参数>){函数体};
delegate void Func1(int i); delegate int Func2(int i); static Func1 t1 =new Func1(delegate(int i) { Console.WriteLine(i); }); static Func2 t2; static void Main(string[] args) { t2 = delegate(int j) { return j; }; t1(2); Console.WriteLine(t2(1)); }
当然在.net 3.0的时候又有了比匿名函数更方便的东西lambda表达式,这儿就不说了。
4.泛型委托
委托也支持泛型的使用
泛型委托原型:
delegate <T1> <委托名><T1,T2,T3...> (T1 t1,T2 t2,T3 t3...)
例子:
delegate T2 A<T1,T2>(T1 t);//定义有两个泛型(T1,T2)的委托,T2作为委托函数返回类型,T1 作为委托函数参数类型
static int test(int t)
{
return t;
}
static void Main(string[] args)
{
A<int, int> a =test;//将泛型委托委托<T1,T2>实例化为<int,int>,即表示有一个int类型 参数且返回类型是int的函数,所以将test用来实例化委托
Console.WriteLine(a(5));//输出5
}
5.委托的多播性
在上面实例化委托的时候看到:必须将一个匹配函数注册到委托上来实例化一个委托对象,但是一个实例化委托不仅可以注册一个函数还可以注册多个函数,注册多个函数后,在执行委托的时候会根据注册函数的注册先后顺序依次执行每一个注册函数
函数注册委托的原型:
<委托类型> <实例化名>+=new <委托类型>(<注册函数>)
例子:CheckDelegate _checkDelegate=new CheckDelegate(CheckMod);//将函数CheckMod注册到委托实例_checkDelegate上
在.net 2.0开始可以直接将匹配的函数注册到实例化委托:
<委托类型> <实例化名>+=<注册函数>
例子:CheckDelegate _checkDelegate+=CheckMod;//将函数CheckMod注册到委托实例_checkDelegate上
之后我们还可以注册多个函数到委托上:
例子:_checkDelegate+=CheckPositive;//将函数CheckPositive注册到委托实例_checkDelegate上
_checkDelegate();//执行这个委托实例会先执行CheckMod()再执行CheckPositive()
实际上使用+=符号的时候会判断
如果此时委托还没有实例化(委托实例为null),它会自动用+=右边的函数实例化委托
如果此时委托已经实例化,它会只把+=右边的函数注册到委托实例上
另外有一点需要注意的是,如果对注册了函数的委托实例从新使用=号赋值,相当于是重新实例化了委托,之前在上面注册的函数和委托实例之间也不再产生任何关系,后面的例子会讲到这点!
当然有+=注册函数到委托,也有-=解除注册
例子:_checkDelegate-=new CheckDelegate(CheckPositive);//解除CheckPositive对_checkDelegate的注册
_checkDelegate-=CheckPositive;//.net 2.0开始可以用这种方式解除注册