从一开始接触C#到现在,委托对我来说都是一道坎,曾经想要避开,可是每次写、看C#程序都避免不了接触这玩意儿,每次都会觉得很憋屈!所以这几天想下点苦功夫一下子彻底搞懂它,下面说说自己的见解吧~~
一、什么是委托对于这个词,一开始接触的时候很陌生,这也导致了我的畏惧心理,后来学习设计模式的时候知道了委托模型,当时也不怎么搞的明白,现在冷静下来自己看看,这里也算入门了。委托,说白了就是一种
相当于c++函数指针的东东(如果对C++函数指针不熟的话这就等于白说了啊,还是看下面的例子吧!)~
首先看一下委托的额声明例子: public void delegate Delete_Delegate(string args);委托的声明中有两个东西是非常重要的,第一个是void,也就是这个委托可以注册(代表)的函数的返回值,其次便是args,这个是该委托可以注册(代表)的函数所使用的参数。换句话说,所有的以string为参数,以void为返回值(这里暂时这么说)的函数都是可以通过这个委托进行注册的!
我想看到这个大家就应该很有感触了,一个方法,除了方法名,最能标志它的东西无非就是返回值和参数,至于修饰符,后面再提,所以委托可以说是抓住了方法的核心成分,置于方法名么,这个自然好理解,如果把方法名也通过委托表现出来,那就不需要委托的存在了,其实后面还有一个用处,那就是多播委托~后面再讲
二、委托的使用
委托的使用分为三个步骤:1.委托声明 2.委托注册 3.委托调用a. 委托声明,举个例子:public void delegate Print(string name);解说同上!
b. 委托注册:现在假设有一个函数:
public void speakEnglish(string say){
Console.WriteLine(say);
}
注册方法:Print english = new Print(speakEnglish);
解说:委托其实可以被看作是一个特殊的类,很明显,这里声明的就是该类的实例english,我们再注意一下这里的构造函数所使用的参数,它是一个方法名,该方法必须和委托声明的方法签名一致,否则会抛出异常;上面只是一种注册方式,还有其他便捷的方法~
c. 委托调用:english("hello"); 这个时候控制台便会输出hello字符,委托调用成功!
#########################################华丽丽的分割线#############################################
以上所描述的是最最基本的委托使用过程,接下来引申开来,继续深入
三、多播委托
多播委托对于委托实现其价值来说真的是很重要的,比如在事件监听中的使用。
多播委托的特点:
1)多播委托,顾名思义,就是说可以通过委托一次调用一个以上的方法;
2)多播委托声明时必须返回void,否则会抛出异常;
3)多播委托一旦遇到一个函数抛出异常,则会停止执行剩余的函数;(这个是可以解决的)
这里也可以体会到为什么不需要把方法名通过委托表现出来的原因。
下面举一个多播委托的例子:延续上面的例子(注:多播委托和一般委托的声明并没有什么决定性区别,唯一要注意的就是上面说的特点2)
假设还有一个方法:
public void speakChinese(string say){
Console.WriteLine(say);
}
下面需要同时调用两个方法,那么怎么通过多播委托实现呢?
Print print = new Print(speakEnlish);
print += speakChinese;
print("Hello, 中国!");
这样的话控制台上就会出现两边Hello,中国字符。
解释:1)很明显,多播委托不管注册多少方法,其方法签名一致的要求是不可以更改的!
2)注册的时候首先声名委托的实例,然后可以通过“+=”运算符添加(注册)更多的方法!同理可知,也可以通过运算符“-=”取消注册;
3)注册完毕后,一旦如同一般委托通过委托实例传入参数则所有注册过的方法都会接收参数运行一遍!并且是没有执行顺序的!
通过3)可以解释为什么返回值必须是void,甚至返回值是同一个类型都不可以!因为多播委托中注册的方法是一起调用的,如果有返回值,比如一个返回1,一个返回2,那么就会出现一个函数(委托)返回多个值的情况,总会出错!所以必须为Void.
到这里,我想具体什么是委托,怎么使用委托基本上比较清楚了,下面我最最关心的就是到底委托会被用在哪些地方呢?委托是C#中比较独特的一个技术,存在即是真理,它的优点在什么地方?(很不幸,我C++没学好,关于C++函数指针的有点也没参透,所以不能迁移过来)
####################################
委托的能力######################################
大家在使用集合时,一定使用过Sort类似的方法,这个方法就是用来对集合中的元素进行排序的,对于一般元素,比如int等等,它内部就嵌有比较方法在里面,然而,很多时候我们需要比较的往往不是这么简单的东西,很多时候我们要比较的是我们自己定义的类型。而Sort方法中也有一个构造函数,是需要我们自己传入比较函数的,这里就是一个使用委托的绝佳范例。下面引用的例子来自《C#高级编程(第6版)》(有兴趣的话可以去看看,书不错,就是厚了点)
程序1:
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4
5 namespace Wrox.ProCSharp.Delegates
6 {
7 class Employee
8 {
9 private string name;
10 private decimal salary;
11
12 public Employee(string name, decimal salary)
13 {
14 this.name = name;
15 this.salary = salary;
16 }
17
18 public override string ToString()
19 {
20 return string.Format("{0}, {1:C}", name, salary);
21 }
22
23 public static bool CompareSalary(object x, object y)
24 {
25 Employee e1 = (Employee)x;
26 Employee e2 = (Employee)y;
27 return (e1.salary < e2.salary);
28 }
29 }
30 }
这个Employee类里面定义了一个比较方法,CompareSalary,这个方法就是等会儿需要注册委托的方法
程序2:
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4
5 namespace Wrox.ProCSharp.Delegates
6 {
7 delegate bool Comparison(object x, object y);
8
9 class BubbleSorter
10 {
11 static public void Sort(object[] sortArray, Comparison comparer)
12 {
13 for (int i = 0; i < sortArray.Length; i++)
14 {
15 for (int j = i + 1; j < sortArray.Length; j++)
16 {
17 if (comparer(sortArray[j], sortArray[i]))
18 {
19 object temp = sortArray[i];
20 sortArray[i] = sortArray[j];
21 sortArray[j] = temp;
22 }
23 }
24 }
25 }
26 }
27 }
这个类里面声明了一个委托,可以看到这个委托和上面Employee类里面的方法签名是一样的,也就是说可以通过这个代理注册上面的CompareSalary方法
程序3:
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4
5 namespace Wrox.ProCSharp.Delegates
6 {
7
8 class Program
9 {
10 static void Main()
11 {
12 Employee[] employees =
13 {
14 new Employee("Bugs Bunny", 20000),
15 new Employee("Elmer Fudd", 10000),
16 new Employee("Daffy Duck", 25000),
17 new Employee("Wiley Coyote", (decimal)1000000.38),
18 new Employee("Foghorn Leghorn", 23000),
19 new Employee("RoadRunner'", 50000)};
20
21 BubbleSorter.Sort(employees, Employee.CompareSalary);
22
23 foreach (var employee in employees)
24 {
25 Console.WriteLine(employee);
26 }
27 //for (int i = 0; i < employees.Length; i++)
28 // Console.WriteLine(employees[i].ToString());
29 }
30 }
31 }
我们看一下这里是怎么使用委托的!
首先声明了一个Employee数组,目标是对里面的元素尽心排序,我们看到,排序使用的方法是静态的Sort()方法,传入了一个数组和一个方法名,而在Sort()方法的参数列表里面大家可以看到,与方法名对应的位置是委托的实例,也就是说,我们把比较的方法通过委托传给了比较函数,这样,我们就能够通过改变比较的方法来实现比较我们自己定义的类型的目的!
个人感觉这样的实现很方便,尤其是将比较函数分离出来,这样符合程序的“开闭原则”,将需要变化,可能变化的部分剥离了出来~~当然,委托的能力不是这样一个例子可以说清楚的,况且这里还没有列出多播委托的例子,在窗体事件中每个多播委托可以实现一个事件激发多个监听者的功能,所以说到底委托有什么好处,该怎么使用,都需要多多实践才能更多的领悟~
接下来还有些东西值得侃一侃:
1.委托之匿名方法:委托不但可以为已经存在的方法注册,而且在编辑过程中可以直接添加一个匿名方法,换句话说,就是临时写一个方法放入注册列表中,下面是一个示例,延续上面的案例:
Print print = delegate(string say){
Console.WriteLine(say);
}这里注册就不是一个方法了,而是一个代码块,关键字是delegate,这是不能更改的,后面的传入参数以及里面方法的返回值(如果有返回值的情况)必须要和委托声明的一致!
这里我有一个疑问,这种匿名方法的委托使用是不是可以用在多播委托中?按常理应该是可以添加的,那么怎么取消注册呢?(没有方法名的情况下是不可能使用-=的嘛~)2.委托:
λ表达式是C#3.0新提供的语法,用于委托类型以简化代码,事实上,就是委托形式的一种符号表达形式,下面是一个示例:
上面的匿名方法的例子是可以这么用表达式写的:
Print print => (say) {
Console.WriteLine(say);
}
上面的表达式很奇怪吧?say这个变量没有实现声明就开始使用了对吧?解释如下:
其实say是声明过的,在哪里声明的呢?对,没错,就是第一次使用的时候声明的,但是很明显,这里根本不像变量声明啊!根本就没有指出类型嘛,这就是委托的独特之处,在声明委托的时候就已经指出了参数的类型,在这里表达式是专门代替委托而存在的,也就相当于在这里指出了say的类型!
下面再看一个例子就知道表达式和委托的关系了:
委托声明:public delegate bool Predicate(int object);
看下面的使用方法:
Predicate p1 = x => x>5;
list.findAll();
第二种使用方式相当于:list.findAll(
delegate(int x){return x>5}
)
;
对比上面两条语句,是否有所感悟?
3.委托之协变与抗变
这个我想也不太常遇到,其实就是委托的参数与返回值和父类与子类之间的一种转换,这里就不罗哩罗嗦了~~
写写写写发现有点像是整理,不过想说的也基本上说在这里了,欢迎留下意见,也欢迎留下问题互相交流~