委托的初步理解和用法

委托是用来处理其他语言需要用函数指针来处理的情况的。

都说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的时候就会依次调用d1d2。每个指向的函数函数的形参列表也都一样。

<委托类型> <实例化名>+=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.      每一个值参数(非refout修饰符的参数)都存在从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类型。

 

 

//以上只是委托的基本概念和用法,与事件结合后使用会比较灵活。

//还有泛型委托和泛型事件。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章