委托的简单使用
本人对委托的理解: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,...);
然后吧,我们的泛型委托也可以如同之前定义的委托那样,完成逆变和协变了。