C#進階之路(一):委托


一、什么是委托

  簡單說它就是一個能把方法當參數傳遞的對象,而且還知道怎么調用這個方法,同時也是粒度更小的“接口”(約束了指向方法的簽名)

  委托是一個類,它定義了方法的類型,使得可以將方法當作另一個方法的參數來進行傳遞,是種將方法動態地賦給參數的做法。

  用過C/C++的,對委托不會陌生,委托可以看成函數指針的升級版本!

  函數指針簡介:

  下面是一段C程序,Calc就是定義的函數指針。

typedef int (* Calc)(int a, int b);

int Add(int a, int b)
{
int result = a + b;
return result;
}
main()
{
int x = 100;

int y = 200;

int z = 0;

Calc funcPoint1 = &Add;

z = funcPoint1(x, y);

printf("%d \n", z);
}

這段程序很好的體現了一切皆地址的思想,變量和函數都是地址。

直接調用和間接調用的效果是一致的,都是訪問那個內存地址,委托相當於函數指針的升級版。

  委托的簡單案例

  一個委托類型定義了該類型的實例能調用的一類方法,這些方法含有同樣的返回類型和同樣參數(類型和個數相同)。

委托是一個類,所以要在類聲明的位置進行聲明,而不是寫在類里面,那樣就寫成嵌套類了。如下定義了一個委托類型 - 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 result = 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;
    //c 就是委托實例,
        int result = c(2);
        Console.Write(result);
        Console.ReadKey();
    }
}

二、委托的一般使用

2.1用委托實現插件式編程

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

  例如,我們有一個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) {
// 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);

...

2.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);
//線程暫停0.1s之后再繼續運行程序!
            }
        }
    }
}

然后我們需要兩個監視進度的方法,一個把進度寫到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 + "%");
    }

}

運行結果:

 

看到這里,是不是發現你已然更加愛上C#了。

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

  當一個類的實例的方法被賦給一個委托對象時,在上下文中不僅要維護這個方法,還要維護這個方法所在的實例。System.Delegate 類的Target屬性指向的就是這個實例。(也就是要在內存中維護這個實例,也就是可能的內存泄漏)

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

2.4泛型委托

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

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]);
    }
}

2.5Func 和 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 是對delegate的一種簡寫,更簡潔

Func 和 Action 委托,除了ref參數和out參數,基本上能適用於任何泛型委托的場景,非常好用。ACTION 和FUNC 最常用的兩種委托,類庫為我們准備好的!

  action就是一種委托的簡便寫法,默認的是無返回值類型的方法,注意不要加括號,只是綁定地址,而不是執行!Func這種用來調用有返回值的委托!

直接調用方法,使用calculator.report();

間接調用,使用action.Invoke();

action();這種寫法是為了模仿函數指針的寫法。實際上還是默認調用invoke()

上面的案例寫的有些亂,而且是一種把func寫入函數參數的類型,不容易理解,下面再用另一個案例

static void Main(string[] args)
{
    Func<string> RetBook = new Func<string>(FuncBook);
    Console.WriteLine(RetBook());
}
public static string FuncBook()
{
    return "送書來了";
}

無返回值

static void Main(string[] args)
{
    Action<string> BookAction = new Action<string>(Book);
    BookAction("百年孤獨");
}
public static void Book(string BookName)
{
    Console.WriteLine("我是買書的是:{0}",BookName);
}

2.6委托的異步調用

1、顯式異步調用

顯式異步調用 thread  或者 task

 

2、隱式異步調用

使用委托進行隱式異步調用,begininvoke就是隱式異步調用,它會開發分支線程,他有兩個參數。

aciont1.BeginInvoke(null, null);

EndInvoke

隱式調用也有兩種,一種是不使用回調函數的,另一種是使用的。

不使用回調函數

namespace delegate3
{
    class Program
    {
        //public delegate int AddHandler(int a, int b);
        public class Cal
        {
            public static int Add(int a, int b)
            {
                Console.WriteLine("開始計算:" + a + "+" + b);
                Thread.Sleep(3000); //模擬該方法運行三秒
                Console.WriteLine("計算完成!");
                return a + b;
            }
        }
        static void Main(string[] args)
        {
            Console.WriteLine("===== 異步調用 AsyncInvokeTest =====");
            //AddHandler handler = new AddHandler(Cal.Add);
            //IAsyncResult: 異步操作接口(interface)
            //BeginInvoke: 委托(delegate)的一個異步方法的開始
            //IAsyncResult result = handler.BeginInvoke(1, 2, null, null);
            Console.WriteLine("繼續做別的事情。。。");

            Func<int,int,int> RetBook = new Func<int,int,int>(Cal.Add);
            //RetBook.BeginInvoke(1, 2);
            IAsyncResult result = RetBook.BeginInvoke(1, 2, null, null);
            //異步操作返回
            Console.WriteLine(RetBook.EndInvoke(result));
            Console.ReadKey();
        }
    }
}
View Code

使用回調函數

namespace CallBack
{ 
    public delegate int AddHandler(int a, int b);
    public class Cal
    {
        public static int Add(int a, int b)
        {
            Console.WriteLine("開始計算:" + a + "+" + b);
            Thread.Sleep(3000); //模擬該方法運行三秒
            Console.WriteLine("計算完成!");
            return a + b;
        }
    }
    class Program
    {
        static void Main()
        {
            Console.WriteLine("===== 異步回調 AsyncInvokeTest =====");
            AddHandler handler = new AddHandler(Cal.Add);
            //異步操作接口(注意BeginInvoke方法的不同!)
            IAsyncResult result = handler.BeginInvoke(1, 2, new AsyncCallback(回調函數), "AsycState:OK");
            Console.WriteLine("繼續做別的事情。。。");
            Console.ReadKey();
        }
        static void 回調函數(IAsyncResult result)
        {
            //result 是“加法類.Add()方法”的返回值
            //AsyncResult 是IAsyncResult接口的一個實現類,空間:System.Runtime.Remoting.Messaging
            //AsyncDelegate 屬性可以強制轉換為用戶定義的委托的實際類。
            AddHandler handler = (AddHandler)((AsyncResult)result).AsyncDelegate;
            Console.WriteLine(handler.EndInvoke(result));
            Console.WriteLine(result.AsyncState);
        }
    }
}
View Code

三、委托的缺點

 

引用了某個方法,那么這個方法在內存中就不能釋放了,一旦釋放,委托就不能調用這個方法,所以委托有可能造成內存泄漏。(靜態方法不存在這個問題)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM