委託使用詳解

委託太常見了,能靈活運用可以使你在編程中游刃有餘。簡單說它就是一個能把方法當參數傳遞的對象,而且還知道怎麼調用這個方法,同事也是力度更小的“接口”

 

委託的簡單使用

一個委託類型定義了該類型的實例能調用一類方法,這些方法含有同樣的返回類型和同樣參數(類型和個數相同)。委託和接口一樣,可以定義在類的外部。如下定義了一個委託類型-Calculator:

delegate int Calculator(int x);

此委託適用於任何有着int返回類型和一個int類型參數的方法,如:

static int Double(int x){ return x * 2;}

創建一個委託實例,將該此方法賦值給委託實例:

Calculator c=new Calculator(Double);

也可以簡寫成:

Calculator c=Double;

int resule=c(2);

下面是完整代碼:

delegate int Calculator(int x);
class Program {

    static int Double(int x) { return x * 2; }
    static void Main(string[] args) {
        Calculator c = Double;
        int result = c(2);

        Console.Write(result);
        Console.ReadKey();
    }
}

用委託實現插件式編程

我們可以利用“委託是一個能把方法作爲參數傳遞的對象”這一特點,來實現一種插件式編程。

例如,我們有一個Utility類,這個類實現一個通用方法(Calculate),用來執行任何有一個整型參數和整型返回值的方法。這樣說有點抽象,下面來看一個例子:

delegate int Calculator(int x);
class Program {

    static int Double(int x) { return x * 2; }
    static void Main(string[] args) {
        int[] values = { 1,2,3,4};
        Utility.Calculate(values, Double);

        foreach (int i in values)
            Console.Write(i + " "); // 2 4 6 8
        Console.ReadKey();
    }
}
class Utility {
    public static void Calculate(int[] values, Calculator c) {
        for (int i = 0; i < values.Length; i++)
            values[i] = c(values[i]);
    }
}

這個例子中的Utility是固定不變的,程序實現了整數的Double功能。我們可以把這個Double方法看做是一個插件,如果將來還要實現諸如求平方、求立方的計算,我們只需向程序中不斷添加插件就可以了。

如果Double方法是臨時的,只調用一次,若在整個程序中不會有第二次調用,那麼我們可以在Main方法中更靈活的使用這種插件式編程,無需先定義方法,使用λ表達是即可,如:

。。。。。。

Utility.Calculate(values,x=>x*2);

。。。。。。

這樣的代碼在我們平時編程的過程中會經常遇到。

多播委託

所有的委託實例都有多播的功能。所謂多播,就像一羣程序員在某招聘網站上填好了求職意向後,某天有個公司發佈了一個和這個和這些程序員求職意向剛好匹配的工作, 然後這些求職者都被通知了-“有一份好工作開始招人了,你們可以直接申請去上班了”。

也就是說,一個委託實例不僅可以指向一個方法,還可以指向多個方法。例如:

MyDelegate d=MyMethod1;//“+=”用來添加,同理“-=”用來移除。

d+=MyMethod2;//d-=MyMethod2

調用時,按照方法被添加的順序依次執行。注意,對於委託,+=和-=對null是不會報錯的,如:

MyDelegate   d;

d+=MyMethod1;//相當於MyDelegate d=MyMethod1;

爲了更好的理解多播委託在開發中的應用,我們模擬一下招聘網的職位匹配小工具來做示例。在職位匹配過程中會有一段處理時間,所以在執行匹配的時候要能看到執行的進度,而且還要把執行的進度和執行情況寫到日誌文件中。在處理完一個步驟時,將分別執行兩個方法來顯示和記錄執行進度。

我們先定義一個委託(ProgressReporter),然後定義一個匹配方法(Match)來執行該委託中的所有方法。如下:

public delegate void ProgressReporter(int percentComplete);
public class Utility {
    public static void Match(ProgressReporter p) {
        if (p != null) {
            for (int i = 0; i <= 10; i++) {
                p(i * 10);
                System.Threading.Thread.Sleep(100);
            }
        }
    }
}

然後我們需要兩個監視進度的方法,一個把進度寫到Console,另一個把進度寫到一個文件。如下:

class Program {
    static void Main(string[] args) {
        ProgressReporter p = WriteProgressToConsole;
        p += WriteProgressToFile;
        Utility.Match(p);
        Console.WriteLine("Done.");
        Console.ReadKey();
    }

    static void WriteProgressToConsole(int percentComplete) {
        Console.WriteLine(percentComplete+"%");
    }
    static void WriteProgressToFile(int percentComplete) {
        System.IO.File.AppendAllText("progress.txt", percentComplete + "%");
    }
}

運行結果:

 

看到這裏,是不是發現很簡單。

靜態方法和實例方法對於委託的區別

當一個類的實例方法被賦給一個委託對象時,在上下文中不僅要維護這個方法,還要維護這個方法所在的實例。System.Delegate類的Target屬性指向的就是這個實例。舉個例子:

class Program {
    static void Main(string[] args) {
        X x = new X();
        ProgressReporter p = x.InstanceProgress;
        p(1);
        Console.WriteLine(p.Target == x); // True
        Console.WriteLine(p.Method); // Void InstanceProgress(Int32)    }

    static void WriteProgressToConsole(int percentComplete) {
        Console.WriteLine(percentComplete+"%");
    }
    static void WriteProgressToFile(int percentComplete) {
        System.IO.File.AppendAllText("progress.txt", percentComplete + "%");
    }
}
class X {
    public void InstanceProgress(int percentComplete) {
        // do something    }
}

但對於靜態方法,System.Delegate類的Target屬性是Null,所以將靜態方法賦值給委託時性能更優。

泛型委託

如果你知道泛型,那麼就是很容易理解泛型委託,說白了就是含有泛型參數的委託,例如:

public delegate T Calculator<T> (T arg);

我們可以把前面的例子改成泛型的例子,如下:

public delegate T Calculator<T>(T arg);
class Program {

    static int Double(int x) { return x * 2; }
    static void Main(string[] args) {
        int[] values = { 1, 2, 3, 4 };
        Utility.Calculate(values, Double);

        foreach (int i in values)
            Console.Write(i + " "); // 2 4 6 8
        Console.ReadKey();
    }
}
class Utility {
    public static void Calculate<T>(T[] values, Calculator<T> c) {
        for (int i = 0; i < values.Length; i++)
            values[i] = c(values[i]);
    }
}

Func和Action委託

有了泛型委託,就有了一個能適用於任何類型和任意參數(類型和合理的個數)的通過委託,Func和Action。如下所示(下面的in表示參數,out表示返回結果):

delegate TResult Func <out TResult> ();delegate TResult Func <in T, out TResult> (T arg);delegate TResult Func <in T1, in T2, out TResult> (T1 arg1, T2 arg2);
... 一直到 T16
delegate void Action ();delegate void Action <in T> (T arg);delegate void Action <in T1, in T2> (T1 arg1, T2 arg2);
... 一直到 T16

有了這樣的委託,我們上面的Calculator泛型委託就可以刪掉了,實例就可以更簡潔了:

public static void Calculate<T>(T[] values, Func<T,T> c) {
    for (int i = 0; i < values.Length; i++)
        values[i] = c(values[i]);
}

Func和Action委託,除了ref參數和out參數,基本上能適用於任何泛型委託的場景,非常好用。

委託的兼容

1.委託的類型兼容

delegate void D1();delegate void D2();
...
D1 d1 = Method1;
D2 d2 = d1;

下面是被允許的:

D2 d2=new D2(d1);

對於具體相同的目標方法的委託是被視爲相等的:

delegate void D();
...
D d1 = Method1;
D d2 = Method1;
Console.WriteLine (d1 == d2); // True

同理,對於多播委託,如果含有相同的方法和相同的順序,也被視爲相同。

2.參數類型兼容

在OOP中,任何使用父類的地方均可以用子類代替,這個OOP思想對委託的參數同樣有效。如:

delegate void StringAction(string s);class Program {
    static void Main() {
        StringAction sa = new StringAction(ActOnObject);
        sa("hello");
    }
    static void ActOnObject(object o) {
        Console.WriteLine(o); // hello
    }
}

3.返回值類型兼容

道理和參數類型兼容一樣:

delegate object ObjectRetriever();class Program {
    static void Main() {
        ObjectRetriever o = new ObjectRetriever(RetriveString);
        object result = o();
        Console.WriteLine(result); // hello    }
    static string RetriveString() { return "hello"; }
}

 

異步委託:

異步委託可以看我之前寫的博客:https://mp.csdn.net/console/editor/html/103785655

也可以看我寫的一個小Demo,裏面詳細寫了委託的用法,GitHub地址:https://github.com/wangongshen/GsDelegateEvent

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