委托是用来处理其他语言需要用函数指针来处理的情况的。
都说C#没有指针,而委托就相当于是个函数指针,不过也有很多不同点,委托是完全面向对象的。
C++中指针仅指向成员函数,委托同时封装了对象实例和方法。
C#的叫法
函数:方法
返回值+参数列表:签名
把它和函数指针放一起说可能会比较好理解委托的作用。
首先,委托是个类,委托声明定义一个从System.Delegate类派生的类。
委托是可以保存对方法的引用的类。与其他的类不同,委托类具有一个签名,并且它只能对与其签名匹配的方法进行引用。这样,委托就等效于一个类型安全函数指针。
委托实例(也就是类的对象)封装了一个调用列表,该列表列出了一个或多个方法,每个方法称为一个可调用实体。
可以说委托对象可以指向很多个其他类的方法,或者说委托这个时候相当于一个函数指针,可以指向其他类的一个或多个成员函数。
当然委托对象所指向的方法的签名,一定要是跟声明委托时的签名一样。也就是返回值和参数列表要一样。
Ø 委托的声明
[委托修饰符] delegate 返回值类型委托名([形参列表]);
蓝色部分就是该委托的签名
委托声明的语法与一般C#类的声明语法不一致。C#编译器会根据委托的声明语法,自动创建一个派生于System.MulticastDelegat的类及其相关实现细节。
简单说,上面的声明在编译的时候,实际上会转换成一个类,类的成员函数自动生成。
Ø 委托的实例化和调用
声明了委托(实际上是个派生类),需要创建委托的实例(对象),然后调用其方法(函数成员)
创建委托实例的基本形式如下:
委托名委托实例名 = new 委托名(匹配方法);
委托名委托实例名 = 匹配方法 ; //等价简写
委托实例名的同步调用与方法的调用:
委托实例名.Invoke(实参列表);
委托实例名(实参列表);
简单看个例子理解委托的实例化和调用
using System;
namespaceCSharpBook.Chapter09
{
delegatevoidD(int x); // 声明委托
classC
{
publicstaticvoid M1(int i){Console.WriteLine("C.M1:" + i);}
publicstaticvoid M2(int i){Console.WriteLine("C.M2:" + i);}
publicvoid M3(int i){Console.WriteLine("C.M3:" + i);}
}
classTest
{
staticvoid Main()
{
D d1 = newD(C.M1); //使用new关键字,创建委托对象,指向类静态方法
d1(-1); //调用M1
D d2 = C.M2; //使用赋值运算符,创建委托对象,指向类静态方法
d2(-2); //调用M2
C objc = newC();
D d3 = newD(objc.M3); //使用new关键字,创建委托对象,指向对象实例方法
d2(-3); //调用M3
Console.ReadKey();
}
}
}
再看个稍微复杂点的
using System;
namespaceCSharpBook.Chapter09
{
delegatevoidD(int[] A); // 声明委托
classArraySort
{
publicstaticvoid DisplayArray(int[] A) //打印数组
{ foreach (int i in A) Console.Write("{0,5} ", i); Console.WriteLine(); }
publicstaticvoid GeneralSort(int[] A, D sort)
{ //通用排序程序
sort(A); // 调用排序算法,委托实例名(实参列表);
Console.WriteLine("升序数组: "); DisplayArray(A); //显示数组
}
publicstaticvoid BubbleSort(int[] A)
{ //冒泡算法
int i, t;
int N = A.Length; //获取数组A的长度N
for (int loop = 1; loop <=N - 1; loop++)//外循环进行N-1轮比较
{ for (i = 0; i <= N -1 - loop; i++) //内循环两两比较,大数下沉
if (A[i] > A[i +1]) //相邻两数交换
{ t = A[i]; A[i] = A[i + 1];A[i + 1] = t; }
}
}
publicstaticvoid SelectSort(int[] A)
{ //选择算法
int i, t, MinI;
int N = A.Length; //获取数组A的长度N
for (int loop = 0; loop<= N - 2; loop++)//外循环进行N-1轮比较
{ MinI = loop;
for (i = loop; i <=N - 1; i++) //内循环中在无序数中找最小值
if (A[i] < A[MinI])MinI = i;
t = A[loop]; A[loop] = A[MinI];A[MinI] = t;//最小值与第一个元素交换
}
}
staticvoid Main()
{ int[] A = newint[10]; Random rNum = newRandom();
//数组A赋值(0~100之间的随机数)
for (int i = 0; i <A.Length; i++) A[i] = rNum.Next(101);
Console.WriteLine("原始数组: "); DisplayArray(A); //显示数组
D d1 = newD(ArraySort.BubbleSort);//创建委托实例,指向冒泡算法
Console.Write("冒泡算法---"); GeneralSort(A, d1);
D d2 = newD(ArraySort.SelectSort); //创建委托实例,指向选择算法
Console.Write("选择算法---"); GeneralSort(A, d2); Console.ReadKey();
}
}
}
Ø 匿名委托
匿名方法。即无须先声明类或结构体以及与委托匹配的方法,而是在创建委托的实例时,直接声明与委托匹配的方法的代码块。
简单说,就是把委托实例所指向的方法直接写在这个委托下面,所以不需要单独声明这个所指向的方法。
声明匿名委托的基本语法为:
委托名委托实例名 = new delegate ([形参列表])
{
方法体;
};
例如
staticvoid Main()
{ // 使用匿名方法实例化delegate类
Printer p = delegate(string j)
{
Console.WriteLine(j);
};
p("使用匿名方法的委托的调用。"); //匿名delegate调用结果
}
Ø 多播委托
个人认为多播委托也是跟函数指针有所区别的一个地方
函数指针指向的是一个内存单元,这个单元指向一个函数。而多播委托就是可以让一个委托对象指向多个函数。
多播委托包括Combine RemoveRemoveAll三个静态方法,用于添加、删除指向的函数到调用列表。也可以用+或+= -或-= 进行添加、删除
委托类 D
创建委托实例d1 d2 d3 d4...假设每个委托实例指向的函数不一样,但签名一定要一样。
d1+=d2;
那么调用d1的时候就会依次调用d1、d2。每个指向的函数函数的形参列表也都一样。
<委托类型> <实例化名>+=new
<委托类型>(<注册函数>)
例子:CheckDelegate _checkDelegate=new CheckDelegate(CheckMod);//将函数CheckMod注册到委托实例_checkDelegate上
也可以直接将匹配的函数注册到实例化委托:
<委托类型> <实例化名>+=<注册函数>
例子:CheckDelegate _checkDelegate+=CheckMod;//将函数CheckMod注册到委托实例_checkDelegate上
另外有一点需要注意的是,如果对注册了函数的委托实例从新使用=号赋值,相当于是重新实例化了委托,之前在上面注册的函数和委托实例之间也不再产生任何关系
Ø 委托的异步调用
//没怎么看懂,先跳过。。。
Ø 委托的兼容性
如果硬性要求委托的签名和所匹配的方法的签名完全一样(返回值类型一样,参数列表一样),用起来就会很局限。
也就是说,委托的签名和所匹配的方法的签名也可以不完全一样,简单来说需要满足以下条件:(假设委托类型D,方法M)
1. D 和M的参数数目相同,且各自对应参数具有相同的ref或out修饰符;
2. 对于每个ref或out参数,D中的参数类型与M中的参数类型相同;
3. 存在从M的返回类型到D的返回类型的隐式引用转换(协变);
4. 每一个值参数(非ref或out修饰符的参数)都存在从D中的参数类型到M中的对应参数类型的隐式引用转换(逆变)
举个例子,
现在有People类,派生出Student类
方法 : Student M(string name);
委托签名 :delegate People D(string name);
Student可以隐式转换成People
方法:void M(People p);
委托签名:delegate void D(Student s)
D的委托实例一样可以指向方法M,兼容
所谓隐式转换,就是系统默认的转换,其本质是小存储容量数据类型自动转换为大存储容量数据类型。
有如下几种:
从sbyte类型到short,int,long,float,double,或decimal类型。
从byte类型到short,ushort,int,uint,long,ulong,float,double,或decimal类型。
从short类型到int,long,float,double,或decimal类型。
从ushort类型到int,uint,long,ulong,float,double,或decimal类型。
从int类型到long,float,double,或decimal类型。 从uint类型到long,ulong,float,double,或decimal类型。
从long类型到float,double,或decimal类型。 从ulong类型到float,double,或decimal类型。
从char类型到ushort,int,uint,long,ulong,float,double,或decimal类型。 从float类型到double类型。
//以上只是委托的基本概念和用法,与事件结合后使用会比较灵活。
//还有泛型委托和泛型事件。。。