【寫給Cpp選手的C#教程】委託篇
委託的簡單使用
本人對委託的理解: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,...);
然後吧,我們的泛型委託也可以如同之前定義的委託那樣,完成逆變和協變了。