理解什麼是委託、事件、Lambad表示式,從回撥說起!

煙雨六月發表於2015-03-12

       接觸委託、事件等知識好長時間了,也反反覆覆看了很多資料,都是一來就給我講委託的語法、用法,卻沒有告訴我到底什麼是委託。要知道什麼是委託,先從回撥說起!

       提示:該博文適合已經學習過委託、事件、Lambad表示式,但學的很模糊的朋友學習,如果你從未學習過這些東西,請看完第一節“1.什麼是回撥?”之後,通過其他更基礎的資料學習之後再繼續往下學習。在學習過程中,希望搭建一個專案跟著做一遍,這樣更好理解。如文中有錯亂之處,望指正!

       Demo下載地址:http://download.csdn.net/detail/wb09100310/8495759

1.什麼是回撥?

       所謂回撥,是指我在A類呼叫B類裡的一個方法,在B類的這個方法執行到某個時刻或條件的時候,又返回來呼叫A類的方法。這是我對回撥的理解,先來看一個列子就好理解了。

示例:

       建立一個控制檯應用程式,命名為DelegateTest,然後新增一個Product類,結構如下圖:

      

 

      我們先看一下Profram.cs和Product的程式碼,再來解釋。

      Program.cs程式碼:

     

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DelegateTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Product product = new Product(){Name = "iPhone6",Price = 5288};//物件初始化語法
            product.GetInfo(product);
            Console.ReadKey();
        }

        public static void Output(Product product)
        {
            Console.Write("產品名稱:");
            Console.WriteLine(product.Name);
            Console.Write("產品價格:");
            Console.WriteLine(product.Price);
        }
    }
}


      Product.cs程式碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DelegateTest
{
    /// <summary>
    /// 產品類
    /// </summary>
    public class Product
    {
        /// <summary>
        /// 產品名字
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 產品價格
        /// </summary>
        public int Price { get; set; }

        public void GetInfo(Product product)
        {
            if (product != null)
            {
                Program.Output(product);
            }

        }
    }
}


       解釋一下,是這樣的。在Program的Main方法裡,我們通過初始化語法例項化了一個Product類,然後呼叫Product類裡的GetInfo方法得到產品資訊,而GetInfo方法又反過來呼叫Program類裡的Output方法輸出產品資訊,這就是回撥。執行結果:

       

        這裡我們沒有用委託,就用我們很普通的方法實現了回撥。然而,我們在實際開發過程中是Product類應該被放到實體層的,而GetInfo方法應該是在業務邏輯層的,也就是說,他們直接的名稱空間是不同的。為了說明,這裡我就把Product類放到Model層,我們建立一個類庫,命名為Model,然後在Model裡新增一個類Product,並把之前Product的類拷貝過來,如果你是直接從DelegateTest裡直接把Product類檔案複製過來,那請修改Product類的名稱空間為Model,結構如圖:

       

       這時候,我們發現Program的Main方法裡找不到Product類了,新增對Model層的引用後可以了。但是GetInfo方法裡找不到Program類了,那我們能不能在Model層反過來新增對DelegateTest的對應呢?不可以的,這時候就可以用委託實現上述回撥功能。

      

 

2.委託

       怎麼定義委託,怎麼使用委託就不說了,網上有的是資料。這裡是如何理解委託,直接看程式碼,然後再解釋。

       Product類中新增委託:

        /// <summary>
        /// 定義一個委託
        /// </summary>
        /// <param name="product"></param>
        public delegate void OutputHandler(Product product);
        /// <summary>
        /// 定義委託成員變數
        /// </summary>
        private OutputHandler outputHandler;
        /// <summary>
        /// 註冊委託方法
        /// </summary>
        /// <param name="handler"></param>
        public void RegHandler(OutputHandler handler)
        {
            outputHandler = handler;
        }

      並修改GetInfo方法為:

        /// <summary>
        /// 得到產品資訊
        /// </summary>
        /// <param name="product"></param>
        public void GetInfo(Product product)
        {
            if (product != null && outputHandler!=null)
            {
                outputHandler(product);
            }

        }



 

       Program裡的Main方法修改如下:

        static void Main(string[] args)
        {
            Product product = new Product(){Name = "iPhone6",Price = 5288};//物件初始化語法
            product.RegHandler(Output);//將Output方法註冊到委託中
            product.GetInfo(product);
            Console.ReadKey();
        }

 

       執行結果同上,解釋一下:

       Product:定義了一個委託,並定義了一個委託變數,由於我們的委託變數為私有的,所以我們還需要再定義一個註冊方法到委託變數的方法RegHandler。在GetInfo中,我們修改後為通過委託變數來回撥Program中註冊到委託變數中的Output方法,這樣就輸出了產品資訊。

       Program:紅色部分,將Output方法註冊到委託變數,這樣在呼叫GetInfo方法的時候自然就能回撥到Output方法了。

       到這裡應該能夠理解委託中回撥是怎麼回事了,也就基本上理解什麼委託了,那麼什麼又是事件呢?

 

3.事件

       事件實際上就是簡化了委託的寫法,主要是簡化了註冊方法。還是一樣,先看程式碼,然後再來解釋。

       Product類中委託修改為:

        /// <summary>
        /// 定義一個委託
        /// </summary>
        /// <param name="product"></param>
        public delegate void OutputHandler(Product product);
        /// <summary>
        /// 定義事件
        /// </summary>
        public event OutputHandler outputHandler;
    刪除了RegHandler方法
     Program裡的Main方法修改如下:
       static void Main(string[] args)
        {
            Product product = new Product() { Name = "iPhone6", Price = 5288 };//物件初始化語法
            product.outputHandler += Output;//註冊事件處理程式
            product.GetInfo(product);
            Console.ReadKey();
        }


 


         執行結果同上,加了event關鍵字之後稱之為事件,在註冊方法的時候用“+=”符號就行,可以新增多個方法。移除方法列表上的方法用“-+”符號。接下來我們再修改一下,以便讓它看上去符合微軟推薦的事件模式。

         先看一下EventArgs類

        

         

    // 摘要: 
    //System.EventArgs 是包含事件資料的類的基類。
    [Serializable]
    [ComVisible(true)]
    public class EventArgs
    {
        // 摘要: 
        // 表示沒有事件資料的事件。
        public static readonly EventArgs Empty;

        // 摘要: 
        // 初始化 System.EventArgs 類的新例項。
        public EventArgs();
    }

         EventArgs表示一個不發任何自定義訊息的事件。對於簡單的事件來說,傳遞的引數是EventArgs實力,如果要自定義傳遞資料引數,需要重新定義一個類,並繼承EventArgs,我們的事件要傳遞的Product類,所以定義應該如下:

        

    public class ProductEventArgs:EventArgs
    {
        public readonly Product product;

        public ProductEventArgs(Product p)
        {
            product = p;
        }
    }


        然後我們的委託修改一下,其他不變。

      

        /// <summary>
        /// 定義一個委託
        /// </summary>
        /// <param name="product"></param>
        public delegate void OutputHandler(object sender,ProductEventArgs e);


         是不是很像ASP.NET控制元件的事件方法,sender是觸發事件的物件,e是要傳遞的事件引數。GetInfo方法修改如下,
       

        /// <summary>
        /// 得到產品資訊
        /// </summary>
        /// <param name="product"></param>
        public void GetInfo(Product product)
        {
            if (product != null && outputHandler!=null)
            {
                outputHandler(this,new ProductEventArgs(product));
            }

        }


          如此,Program類的Output方法修改如下,其他不變:

    class Program
    {
        static void Main(string[] args)
        {
            Product product = new Product() { Name = "iPhone6", Price = 5288 };//物件初始化語法
            product.outputHandler += Output;//註冊事件處理程式
            product.GetInfo(product);
            Console.ReadKey();
        }

        public static void Output(object sender,ProductEventArgs e)
        {
            Console.Write("產品名稱:");
            Console.WriteLine(e.product.Name);
            Console.Write("產品價格:");
            Console.WriteLine(e.product.Price);
        }
    }

         上面只是依照微軟的事件模式,效果和前面是一樣的。

 

4.匿名方法

        思考一下我們發現,Output方法很少會被委託之外的任何程式呼叫,手工定義一個由委託物件呼叫的獨立方法會顯的很繁瑣,於是C#提供了匿名方法,我們可以如下改下Program類,Output方法需要了,執行結果同上:


 

    class Program
    {
        static void Main(string[] args)
        {
            Product product = new Product() { Name = "iPhone6", Price = 5288 };//物件初始化語法
            //product.outputHandler += Output;//註冊事件處理程式
            product.outputHandler += delegate(object sender, ProductEventArgs e)
            {
                Console.Write("產品名稱:");
                Console.WriteLine(e.product.Name);
                Console.Write("產品價格:");
                Console.WriteLine(e.product.Price);
            };
            product.GetInfo(product);
            Console.ReadKey();
        }

    }


5.Lambad表示式

         Lambad表示式只是用更簡單的方法來寫匿名方法,先貼程式碼再說,為了容易理解,我們再在Product類裡新增一個委託

        

        /// <summary>
        /// 定義一個委託
        /// </summary>
        /// <param name="product"></param>
        public delegate int CompareHandler(int p);

          如果價格大於5000返回1,否則返回0,Program類修改如下:

         

    class Program
    {
        static void Main(string[] args)
        {
            Product product = new Product() { Name = "iPhone6", Price = 5288 };//物件初始化語法
            product.GetInfo(product);
            Product.CompareHandler c = i => i > 5000 ? 1 : 0;
            int r = c(product.Price);
            Console.Write(r);
            Console.ReadKey();
        }

    }


        解釋一下:i是引數列表,和定義一個方法引數列表一樣,多個引數用“,”隔開,可以加上引數型別,=>符號後面的語句是處理引數列表的語句。那如果是輸出之前的產品資訊該怎麼寫呢?,Progarm修改如下:

       

class Program
    {
        static void Main(string[] args)
        {
            Product product = new Product() { Name = "iPhone6", Price = 5288 };//物件初始化語法
            //product.outputHandler += Output;//註冊事件處理程式
            //product.outputHandler += delegate(object sender, ProductEventArgs e)
            //{
            //    Console.Write("產品名稱:");
            //    Console.WriteLine(e.product.Name);
            //    Console.Write("產品價格:");
            //    Console.WriteLine(e.product.Price);
            //};
            //product.GetInfo(product);
            product.outputHandler += (object sender, ProductEventArgs e) =>
            {
                Console.Write("產品名稱:");
                Console.WriteLine(e.product.Name);
                Console.Write("產品價格:");
                Console.WriteLine(e.product.Price);
            };
            product.GetInfo(product);
            Console.ReadKey();
        }

    }

    

        OK,這塊知識終於理順了,部落格也整理的幾個小時,希望對大家有用!

         
 

相關文章