本人对委托的理解:C中的函数指针。用一个变量存储函数,方便传递和使用。
按照如下方法使用:
delegate int Dele(int a); class Program { static int pow(int a) { return a * a; } static void Main(string[] args) { //全写为dele myPow = new dele(pow); Dele myPow = pow; //全写为myPow.Invoke(3); myPow(3); } }
很容易想到,我们可以往函数中传入委托,进行解耦。
参数或返回值为委托的函数被称为高阶函数(high-order function)
delegate int Dele(int x); class Program { static int Square(int x) { return x * x; } static void Main() { int[] values = { 1, 2, 3 }; Transform(values, Square); } //Transform就是一个高阶函数 static void Transform(int[] values, Dele t) { for (int i = 0; i < values.Length; i++) //values[i] = Square(values[i]) { values[i] = t(values[i]); } } }
我们的委托可以存放一个函数列表,调用时调用整个列表。本质是new一个委托对象,然后重新赋值。
delegate int NumDele(int a); class Program { static int Pow2(int a) { return a * a; } static int Pow3(int a) { return a * a * a; } static int Pow4(int a) { return a * a * a * a; } static void Main() { NumDele d = null; //往函数列表中增加新的函数 d += Pow2; d += Pow3; d += Pow4; //现在d中的内容:Pow2->Pow3->Pow4 //删除指定函数 d -= Pow3; //现在d中的内容:Pow2->Pow4 Console.Write(d(3)); //返回值为3 * 3 * 3 * 3 = 81,Pow2的返回值被丢弃 } }
你若细看便会发现,上面给委托赋值的函数全部都是static静态函数。
如果我们将实例A的函数F赋值给委托C,那么委托C不仅需要考虑函数F,还需要考虑实例A
可以通过如下方法获取实例:
delegate int NumDele(int a); class Source { public int Pow2(int a) { return a * a; } } class Program { static void Main() { Source s = new Source(); NumDele d = s.Pow2; Console.WriteLine(d.Target);//TestSharp.Source Console.WriteLine(d.Target == s);//True Console.WriteLine(d.Method);//Int32 Pow2(Int32) } }
委托可以包括泛型类型参数:
public delegate T Dele<T>(T prop);
之后我们就可以进行一些诸如Cpp中algorithm库中的一些,泛型算法的操作(不过下面这个例子并不是泛型算法)
public delegate T Dele<T>(T val); class Program { static int square(int x) { return x * x; } static double devide(double x) { return x / 2; } static void changeArray<T>(T[] arr, Dele<T> opt) { for (int i = 0; i < arr.Length; i++) arr[i] = opt(arr[i]); } static void Main() { int[] iArr = { 1, 2, 3 }; double[] dArr = { 5.4, 2.7, 9.8 }; //对于int类型的数组,使用square函数对其进行加工 changeArray(iArr, square); //iArr变成了[1 , 4 , 9] //对于double类型的数组,使用devide函数对其进行加工 changeArray(dArr, devide); //dArr变成了[2.7 , 1.35 , 4.9] } }
如果我们经常需要用到泛型委托,然后每次都需要自己进行定义和声明,那很明显很烦人。后面C#很贴心的减少了我们的套路活,整出了Func<>和Action<>。
对于Func<T1,T2,T3...>而言,最后一个T是返回值,其余的T都是参数。
对于Action<T1,T2,T3...>而言,所有的T都是参数。
//public delegate T Dele<T>(T val); //static void changeArray<T>(T[] arr, Dele<T> opt) static void changeArray<T>(T[] arr,Func<T,T> opt) //使用Func的写法,就不需要声明Dele了
①委托的类型不同,不能赋值
②委托指向相同函数,则认为其是等价的
public delegate void D1(); public delegate void D2(); class Program { static void Func() {} static void Main() { D1 d1 = Func; //D2 d2 = d1; 如果这么写会报错 D2 d2 = new D2(d1); //这么写才行 D1 d3 = Func; D2 d4 = Func; d3==d4 //True } }
③跟委托绑定的函数,其参数可以是委托的参数的父类、子类
public delegate void StringDele(string s); class Program { static void ObjectFunc(object o) { } static void Main() { StringDele s = ObjectFunc; s("hello world"); //string参数会隐式变换为object参数 } }
一般的,传子类是正常的多态行为。传父类被称为逆变。
④跟委托绑定的函数,其返回值可以是委托的返回值的父类、子类
public delegate object objectDele(); class Program { static string strFuc() { return "hello"; } static void Main() { objectDele s = strFuc; s(); } }
objectDele想要返回object,但如果绑定的函数返回object的子类,那么也是可以的。
函数的返回值是委托返回值的子类,这种情况被称为协变。
⑤泛型委托相关
我们需要将只用于返回值的类型参数标记为协变(out),将只用于参数的类型参数标记为逆变(in)
其实,Func和Action的定义可以如下理解:
delegate TResult Func<in T1,in T2,...,out TResult>(T1 prop1,T2 prop2,...); delegate void Action<in T1,in T2,...>(T1 prop1,T2 prop2,...);
然后吧,我们的泛型委托也可以如同之前定义的委托那样,完成逆变和协变了。