C# 委托、事件,lamda表達式


參考文章

1. 委托Delegate

     C#中的Delegate對應於C中的指針,但是又有所不同C中的指針既可以指向方法,又可以指向變量,並且可以進行類型轉換,

C中的指針實際上就是內存地址變量,他是可以直接操作內存的,通過內存地址直接訪問變量,直接調用方法。

     而C#中的Delegate是強類型的,也就是說在聲明委托時就已經指定了該變量只能指向具有特定參數,以及返回值的方法。

    使用delegate就可以直接建立任何名稱的委托類型,當進行系統編譯時,系統就會自動生成此類型。您可以使用delegate void MyDelegate()
方式建立一個委托類,並使用ILDASM.exe觀察其成員。由ILDASM.exe 中可以看到,它繼承了System.MulticastDelegate類,

並自動生成BeginInvoke、EndInvoke、Invoke 等三個常用方法。

image

Invoke 方法是用於同步調用委托對象的對應方法,而BeginInvoke、EndInvoke是用於以異步方式調用對應方法的。

public class MyDelegate:MulticastDelegate
      {
          //同步調用委托方法
          public virtual void Invoke();
          //異步調用委托方法
          public virtual IAsyncResult BeginInvoke(AsyncCallback callback,object state);
          public virtual void EndInvoke(IAsyncResult result);
      }

MulticastDelegate是System.Delegate的子類,它是一個特殊類,編譯器和其他工具可以從此類派生,但是自定義類不能顯式地從此類進行派生。它支持多路廣播委托,並擁有一個帶有鏈接的委托列表,在調用多路廣播委托時,系統將按照調用列表中的委托出現順序來同步調用這些委托。

MulticastDelegate具有兩個常用屬性:Method、Target。其中Method 用於獲取委托所表示的方法Target 用於獲取當前調用的類實例。

 

1.1 委托的使用

當建立委托對象時,委托的參數類型必須與委托方法相對應。只要向建立委托對象的構造函數中輸入方法名稱example.Method,委托就會直接綁定此方法。使用myDelegate.Invoke(string message),就能顯式調用委托方法。但在實際的操作中,我們無須用到 Invoke 方法,而只要直接使用myDelegate(string message),就能調用委托方法。

無返回值的委托

class Program
     {
         delegate void MyDelegate(string message);
 
         public class Example
         {
             public void Method(string message)
             {
                 MessageBox.Show(message);
             }
         }
 
         static void Main(string[] args)
         {
             Example example=new Example();
             MyDelegate myDelegate=new MyDelegate(example.Method);
             myDelegate("Hello World");
             Console.ReadKey();
         }
     }

有返回值的委托

class Program
     {
         delegate string MyDelegate(string message);
 
         public class Example
         {
             public string Method(string name)
             {
                 return "Hello " + name;
             }
         }
 
         static void Main(string[] args)
         {
             Example example=new Example();
             //綁定委托方法
             MyDelegate myDelegate=new MyDelegate(example.Method);
             //調用委托,獲取返回值
             string message = myDelegate("Leslie");
             Console.WriteLine(message);
             Console.ReadKey();
         }
     }

 

多路廣播委托

delegate double MyDelegate(double message);
 
         public class Price
         {
             public double Ordinary(double price)
             {
                 double price1 = 0.95 * price;
                 Console.WriteLine("Ordinary Price : "+price1);
                 return price1;
             }
 
             public double Favourable(double price)
             {
                 double price1 = 0.85 * price;
                 Console.WriteLine("Favourable Price : " + price1);
                 return price1;
             }
 
             static void Main(string[] args)
             {
                 Price price = new Price();
                 //綁定Ordinary方法
                 MyDelegate myDelegate = new MyDelegate(price.Ordinary);
                 //綁定Favourable方法
                 myDelegate += new MyDelegate(price.Favourable);
                 //調用委托
                 Console.WriteLine("Current Price : " + myDelegate(100));
                 Console.ReadKey();
             }
         }

輸出

image

 

1.2 委托的協變與逆變

     前面已經說過,委托是強類型的方法指針,但是在面對具有繼承關系類型的參數、或者返回值時,委托是如何處理的呢。

協變(返回值類型具有繼承關系的方法)

public class Worker
      {.......}
      public class Manager:Worker
      {.......}
  
       class Program
      {
          public delegate Worker GetWorkerHandler(int id);
          //在 Framework2.0 以上,委托 GetWorkerHandler 可綁定 GetWorker 與 GetManager 兩個方法  
          public static Worker GetWorker(int id)
          {
              Worker worker = new Worker();
              return worker;
          }
  
          public static Manager GetManager(int id)
          {
              Manager manager = new Manager();
              return manager;
          }
  
         static void Main(string[] args)
         {
             GetWorkerHandler workerHandler = new GetWorkerHandler(GetWorker);
             Worker worker=workerHandler(1);
             GetWorkerHandler managerHandler = new GetWorkerHandler(GetManager);
             Manager manager = managerHandler(2) as Manager;
             Console.ReadKey();
         }
      }

委托 GetWorkerHandler 可以綁定 GetWorker 與 GetManager 兩個方法

 

逆變

委托逆變,是指委托方法的參數同樣可以接收 “繼承” 這個傳統規則。像下面的例子,以 object 為參數的委托,可以接受任何 object 子類的對象作為參數。最后可以在處理方法中使用 is 對輸入數據的類型進行判斷,分別處理對不同的類型的對象。

class Program
     {
         public delegate void Handler(object obj);
 
         public static void GetMessage(object message)
         {
             if (message is string)
                 Console.WriteLine("His name is : " + message.ToString());
             if (message is int)
                 Console.WriteLine("His age is : " + message.ToString());
         }
 
         static void Main(string[] args)
         {
             Handler handler = new Handler(GetMessage);
             handler(29);
             Console.ReadKey();
         }
    }

 

注意委托與其綁定方法的參數必須一至,即當 Handler 所輸入的參數為 object 類型,其綁定方法 GetMessage 的參數也必須為 object 。否則,即使綁定方法的參數為 object 的子類,系統也無法辨認。

 

大家可能注意到了,這個委托方法GetMessage的實現不是那么優雅,於是泛型委托應運而生。

 

class Program { public delegate void Handler<T>(T obj); public static void GetWorkerWages(Worker worker) { Console.WriteLine("Worker's total wages is " + worker.Wages); } public static void GetManagerWages(Manager manager) { Console.WriteLine("Manager's total wages is "+manager.Wages); } static void Main(string[] args) { Handler<Worker> workerHander = new Handler<Worker>(GetWorkerWages); Worker worker = new Worker(); worker.Wages = 3000; workerHander(worker); Handler<Manager> managerHandler = new Handler<Manager>(GetManagerWages); Manager manager = new Manager(); manager.Wages = 4500; managerHandler(manager); Console.ReadKey(); } }

 

2. event事件的由來

事件是特殊的委托,他為委托提供了封裝性,一方面允許從類的外部增加,刪除綁定方法,另一方面又不允許從類的外部來觸發委托所綁定了方法。

 

public delegate double PriceHandler();
 
     public class PriceManager
     {
         public PriceHandler GetPriceHandler;
 
         //委托處理,當價格高於100元按8.8折計算,其他按原價計算
         public double GetPrice()
         {
             if (GetPriceHandler.GetInvocationList().Count() > 0)
             {
                 if (GetPriceHandler() > 100)
                     return GetPriceHandler()*0.88;
                 else
                     return GetPriceHandler();
             }
             return -1;
         }
     }
 
     class Program
     {
         static void Main(string[] args)
         {
             PriceManager priceManager = new PriceManager();
             
             //調用priceManager的GetPrice方法獲取價格
             //直接調用委托的Invoke獲取價格,兩者進行比較
             priceManager.GetPriceHandler = new PriceHandler(ComputerPrice);
             Console.WriteLine(string.Format("GetPrice\n  Computer's price is {0}!",
                 priceManager.GetPrice()));
             Console.WriteLine(string.Format("Invoke\n  Computer's price is {0}!",
                 priceManager.GetPriceHandler.Invoke()));
             
             Console.WriteLine();
             
             priceManager.GetPriceHandler = new PriceHandler(BookPrice);
             Console.WriteLine(string.Format("GetPrice\n  Book's price is {0}!",
                 priceManager.GetPrice()));
             Console.WriteLine(string.Format("Invoke\n  Book's price is {0}!" ,
                 priceManager.GetPriceHandler.Invoke()));
             
             Console.ReadKey();
         }
         //書本價格為98元
         public static double BookPrice()
         {
             return 98.0;
         }
         //計算機價格為8800元
         public static double ComputerPrice()
         {
             return 8800.0;
         }
     }

以上代碼實現了對於100元以上商品的的88折處理。一方面為了給GetPriceHandler綁定方法就必須將委托聲明為public,但是一旦聲明為public

就可以在類外部直接通過Invoke來調用該委托所綁定的方法,而產生我們不需要的結果。

image

 

當然我們可以將GetPriceHandler聲明為private並且通過public 的addHandler,removeHandler來消除委托public的副作用,但是C#提供了更加優雅的方法:

那就是event關鍵字。

 

事件(event)可被視作為一種特別的委托,它為委托對象隱式地建立起add_XXX、remove_XXX 兩個方法,用作注冊與注銷事件的處理方法。而且事件對應的變量成員將會被視為 private 變量,外界無法超越事件所在對象直接訪問它們,這使事件具備良好的封裝性,而且免除了add_XXX、remove_XXX等繁瑣的代碼。

public class EventTest
     {
         public delegate void MyDelegate();
         public event MyDelegate MyEvent;
     }

 

觀察事件的編譯過程可知,在編譯的時候,系統為 MyEvent 事件自動建立add_MyEvent、remove_MyEvent 方法。

image

 

事件能通過+=和-=兩個方式注冊或者注銷對其處理的方法,使用+=與-=操作符的時候,系統會自動調用對應的 add_XXX、remove_XXX 進行處理。
值得留意,在PersonManager類的Execute方法中,如果 MyEvent 綁定的處理方法不為空,即可使用MyEvent(string)引發事件。但如果在外界的 main 方法中直接使用 personManager.MyEvent (string) 來引發事件,系統將引發錯誤報告。這正是因為事件具備了良好的封裝性,使外界不能超越事件所在的對象訪問其變量成員。

 

注意在事件所處的對象之外,事件只能出現在+=,-=的左方。

 

public delegate void MyDelegate(string name);
 
     public class PersonManager
     {
         public event MyDelegate MyEvent;
 
         //執行事件
         public void Execute(string name)
         {
             if (MyEvent != null)
                 MyEvent(name);
         }
     }
 
     class Program
     {
         static void Main(string[] args)
         {
             PersonManager personManager = new PersonManager();
             //綁定事件處理方法
             personManager.MyEvent += new MyDelegate(GetName);
             personManager.Execute("Leslie");
             Console.ReadKey();
         }
 
         public static void GetName(string name)
         {
             Console.WriteLine("My name is " + name);
         }
     }

 

在綁定事件處理方法的時候,事件出現在+=、-= 操作符的左邊,對應的委托對象出現在+=、-= 操作符的右邊。對應以上例子,事件提供了更簡單的綁定方式,只需要在+=、-= 操作符的右方寫上方法名稱,系統就能自動辯認。

 

public delegate void MyDelegate(string name);
 
     public class PersonManager
     {
         public event MyDelegate MyEvent;
         .........
     }
 
     class Program
     {
         static void Main(string[] args)
         {
             PersonManager personManager = new PersonManager();
             //綁定事件處理方法
             personManager.MyEvent += GetName;
             .............
         }
 
         public static void GetName(string name)
         {.........}
    }

如果覺得編寫 GetName 方法過於麻煩,你還可以使用匿名方法綁定事件的處理。

 

public delegate void MyDelegate(string name);
 
     public class PersonManager
     {
         public event MyDelegate MyEvent;
 
         //執行事件
         public void Execute(string name)
         {
             if (MyEvent != null)
                 MyEvent(name);
         }
 
         static void Main(string[] args)
         {
             PersonManager personManager = new PersonManager();
             //使用匿名方法綁定事件的處理
             personManager.MyEvent += delegate(string name){
                 Console.WriteLine("My name is "+name);
             };
             personManager.Execute("Leslie");
             Console.ReadKey();
         }
     }
 

3. lambda表達式

 

在Framework 2.0 以前,聲明委托的唯一方法是通過方法命名,從Framework 2.0 起,系統開始支持匿名方法。
通過匿名方法,可以直接把一段代碼綁定給事件,因此減少了實例化委托所需的編碼系統開銷。
而在 Framework 3.0 開始,Lambda 表達式開始逐漸取代了匿名方法,作為編寫內聯代碼的首選方式。總體來說,Lambda 表達式的作用是為了使用更簡單的方式來編寫匿名方法,徹底簡化委托的使用方式。

使用匿名方法

static void Main(string[] args)
         {
             Button btn = new Button();
             btn.Click+=delegate(object obj,EventArgs e){
                 MessageBox.Show("Hello World !");
             };
         }

使用lambda表達式

static void Main(string[] args)
         {
             Button btn = new Button();
             btn.Click+=(object obj,EventArgs e)=>{
                 MessageBox.Show("Hello World !");
             };
         }

 

3.1常用泛型委托

public delegate bool Predicate<T>(T obj)

它是一個返回bool的泛型委托,能接受一個任意類型的對象作為參數。

class Program
     {
         static void Main(string[] args)
         {
             List<Person> list = GetList();
             //綁定查詢條件
             Predicate<Person> predicate = new Predicate<Person>(Match);
             List<Person> result = list.FindAll(predicate);
             Console.WriteLine(“Person count is : ” + result.Count);
             Console.ReadKey();
         }
         //模擬源數據
         static List<Person> GetList()
         {
             var personList = new List<Person>();
             var person1 = new Person(1,"Leslie",29);
             personList.Add(person1);
             ........
             return personList;
         }
         //查詢條件
         static bool Match(Person person)
         {
             return person.Age <= 30;
         }
     }
 
     public class Person
     {
         public Person(int id, string name, int age)
         {
             ID = id;
             Name = name;
             Age = age;
         }
 
         public int ID
         { get; set; }
         public string Name
         { get; set; }
         public int Age
         { get; set; }
     }
 

Action<T> 的使用方式與 Predicate<T> 相似,不同之處在於 Predicate<T> 返回值為 bool ,  Action<T> 的返回值為 void。
Action 支持0~16個參數,可以按需求任意使用。

public delegate void Action()

public delegate void Action<T1>(T1 obj1)
public delegate void Action<T1,T2> (T1 obj1, T2 obj2)
public delegate void Action<T1,T2,T3> (T1 obj1, T2 obj2,T3 obj3)
............
public delegate void Action<T1,T2,T3,......,T16> (T1 obj1, T2 obj2,T3 obj3,......,T16 obj16)

static void Main(string[] args)
         {
             Action<string> action=ShowMessage;
             action("Hello World");
             Console.ReadKey();
         }
 
         static void ShowMessage(string message)
         {
             MessageBox.Show(message);
         }
 

委托 Func 與 Action 相似,同樣支持 0~16 個參數,不同之處在於Func 必須具有返回值

public delegate TResult Func<TResult>()

public delegate TResult Func<T1,TResult>(T1 obj1)

public delegate TResult Func<T1,T2,TResult>(T1 obj1,T2 obj2)

public delegate TResult Func<T1,T2,T3,TResult>(T1 obj1,T2 obj2,T3 obj3)

............

public delegate TResult Func<T1,T2,T3,......,T16,TResult>(T1 obj1,T2 obj2,T3 obj3,......,T16 obj16)

static void Main(string[] args)
         {
             Func<double, bool, double> func = Account;
             double result=func(1000, true);
             Console.WriteLine("Result is : "+result);
             Console.ReadKey();
         }
 
         static double Account(double a,bool condition)
         {
             if (condition)
                 return a * 1.5;
             else
                 return a * 2;
         }

 

 

3.2 lambda表達式

Lambda 的表達式的編寫格式如下:

     x=> x * 1.5

當中 “ => ” 是 Lambda 表達式的操作符,在左邊用作定義一個參數列表,右邊可以操作這些參數。

例子一, 先把 int x 設置 1000,通過 Action 把表達式定義為 x=x+500 ,最后通過 Invoke 激發委托。

static void Main(string[] args)
         {
             int x = 1000;
             Action action = () => x = x + 500;
             action.Invoke();
 
             Console.WriteLine("Result is : " + x);
             Console.ReadKey();
         }

 

例子二,通過 Action<int> 把表達式定義 x=x+500, 到最后輸入參數1000,得到的結果與例子一相同。
注意,此處Lambda表達式定義的操作使用 { } 括弧包括在一起,里面可以包含一系列的操作。

static void Main(string[] args)
         {
             Action<int> action = (x) =>
             {
                 x = x + 500;
                 Console.WriteLine("Result is : " + x);
             };
             action.Invoke(1000);
             Console.ReadKey();
         }


免責聲明!

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



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