編寫高質量程式碼改善C#程式的157個建議[C#閉包的陷阱、委託、事件、事件模型]...

weixin_34126215發表於2014-05-19

前言

本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html 。本文主要學習記錄以下內容:

  建議38、小心閉包中的陷阱

  建議39、瞭解委託的實質

  建議40、使用event關鍵字對委託施加保護

  建議41、實現標準的事件模型

建議38、小心閉包中的陷阱

  首先我們先來看一段程式碼:

    class Program
    {
        static void Main(string[] args)
        {
            List<Action> list = new List<Action>();
            for (int i = 0; i < 5; i++)
            {
                Action t = () =>Console.WriteLine(i.ToString());
                list.Add(t);
            }
            foreach (Action t in list)
            {
                t();
            }
                Console.ReadLine();
        }
    }

你設想的結果或許是0,1,2,3,4

但沒想到執行後結果如下

通過IL可以檢視程式碼,組合後大致程式碼如下:

    public class TempClass
    {
        public int i;
        public void TempFunc()
        {
            Console.WriteLine(i.ToString());
        }

    } 
    class Program
    {
        static void Main(string[] args)
        {
            List<Action> list = new List<Action>();
            TempClass tempClass = new TempClass();
            for (tempClass.i = 0; tempClass.i < 5; tempClass.i++)
            {
                Action t = tempClass.TempFunc;
                list.Add(t);
            }
            foreach (Action t in list)
            {
                t();
            }
                Console.ReadLine();
        }
    }

當然執行後結果還是5,5,5,5,5

其實這段程式碼所演示的就是一個閉包物件。所謂的閉包物件,指的是上面這種情形中的TempClass物件,如果匿名方法(Lambda表示式)引用了某個區域性變數,編譯器就會自動將該引用提升到該閉包物件中,即將for迴圈中的變數i修改成了引用閉包物件的公共變數i。這樣一來,即使程式碼執行後離開了原區域性變數i的作用域(如for迴圈),包含該閉包物件的作用域也還存在。

下面簡單修改一下之前的程式碼

    class Program
    {

        static void Main(string[] args)
        {
            List<Action> list = new List<Action>();
            for (int i = 0; i < 5; i++)
            {
                int temp = i;
                Action t = () => Console.WriteLine(temp.ToString());
                list.Add(t);
            }
            foreach (Action t in list)
            {
                t();
            }
            Console.ReadLine();
        }
    }

執行結果如下:

建議39、瞭解委託的實質

 http://www.cnblogs.com/aehyok/archive/2013/03/22/2976356.html這裡有我之前對委託的簡單的學習過程,雖然在工作中很少用,幾乎就沒用。不過還是拿來學習學習。

 理解委託需要把握兩個點:

1、委託是方法指標。

2、委託就是一個類。當對其進行例項化的時候,要將引用方法作為它建構函式的引數。

建議40、使用event關鍵字對委託施加保護

 http://www.cnblogs.com/aehyok/archive/2013/02/22/2922586.html 這也是對於事件的簡單理解學習。

建議41、實現標準的事件模型

我們應該知道微軟為事件模型設定的幾個規範:

1、委託型別的名稱以EventHandler結束。

2、委託原型返回值為void。

3、委託原型具有兩個引數:sender表示事件觸發者,e表示事件引數。

4、事件引數的名稱以EventArgs結束。

    public class FileUploadedEventArgs : EventArgs
    {
        public int FileProgress { get; set; }
    }

    public class FileUploader
    {
        public event EventHandler<FileUploadedEventArgs> FileUploaded;

        public void Upload()
        {
            FileUploadedEventArgs e = new FileUploadedEventArgs() { FileProgress=100 };
            while (e.FileProgress > 0)
            {
                ///傳輸程式碼,省略
                e.FileProgress--;
                if (FileUploaded != null)
                {
                    FileUploaded(this, e);
                }
            }
        }
    }

最終進行呼叫的程式碼如下:

    class Program
    {
        static void Main(string[] args)
        {
            FileUploader fileUploader = new FileUploader();
            fileUploader.FileUploaded += Progress;
            fileUploader.Upload();
            Console.ReadLine();
        }

        static void Progress(object sender,FileUploadedEventArgs e)
        {
            Console.WriteLine(e.FileProgress);
        }
    }

 

 

 

相關文章