C#基础加强(8)之委托和事件
委托
簡介
委托是一種可以聲明出指向方法的變量的數據類型。
聲明委托的方式
格式:?delegate <返回值類型> 委托類型名(參數)?,例如:
delegate void MyDel(string str) 。 // 注意:這里除了前面的 delegate 關鍵字,剩下部分和聲明一個函數相同,但是 MyDel 不是函數名,而是委托類型名。創建委托類型變量
聲明委托變量的方式與聲明變量相同,都是通過?new?關鍵字,例:
MyDel sayHello = new MyDel(SayHello); /** SayHello 是一個方法句柄,并且它的返回值需要與 MyDel 的參數返回值相同;* sayHello 這個委托變量就指向 SayHello 方法*/還有一種簡化的寫法:
MyDel sayHello = SayHello; /** 反編譯查看如下:* MyDel sayHello = new MyDel(SayHello);* 即其實與原始寫法相同*/委托的使用
要使用委托可以直接使用?<委托變量名>()?的方式調用委托指向的方法,如果有參數就傳遞參數,例:
using System; using NUnit.Framework;namespace MyTests {[TestFixture]public class Tests{delegate void MyDel(string str);void SayHello(string name){Console.WriteLine("hello " + name);}[Test]public void Test(){MyDel sayHello = SayHello;sayHello("張三");/** hello 張三*/}} } MyTests.Tests委托變量之間可以互相賦值,其實就是一個傳遞方法指針的過程,如:
using System; using NUnit.Framework;namespace MyTests {[TestFixture]public class Tests{delegate void MyDel(string str);void SayHello(string name){Console.WriteLine("hello " + name);}void SayName(string name){Console.WriteLine(name);}[Test]public void Test(){MyDel sayHello = SayHello;sayHello = SayName; // sayHello 本來指向 SayHello 方法,這一行讓其指向了 SayName 方法sayHello("張三"); // 所以實際執行的是 SayName 方法/** 張三*/}} } MyTests.Tests案例一:獲取最大值
先從一個簡單的需求開始,如果我們需要編寫一個獲取 int 數組中最大值的方法,很簡單如下:
1 int GetMaxNum(int[] nums) 2 { 3 int max = nums[0]; 4 for (var i = 1; i < nums.Length; i++) 5 { 6 if (nums[0] > max) max = nums[0]; 7 } 8 return max; 9 }假如又有一個要求,我們定義一個獲取 string 數組中最大值(每個 string 變量都可轉型為 int 變量)的方法,顯示上述方法就不適用了。那有沒有什么方法能夠讓其通用起來呢?
如果我們要獲取 string 數組中的最大值,顯然我們需要先將其中每個元素轉換到 int 類型,然后再進行比較,即重點就是我們要如何定義它的比較規則?
上述代碼的比較規則是在第 6 行的 if 塊中,我們要做的就是讓那個這個 if 塊中的內容動態起來,此時委托就派上用場了,看如下代碼:
/*** 獲取數組中的最大值*/ object GetMax(object[] nums,CompareFunc compareFunc) {object max = nums[0];for (var i = 1; i < nums.Length; i++){if (compareFunc(nums[i],max))max = nums[i];}return max; }/*** 如果 obj1 比 obj2 大,則返回 true,否則返回 false*/ delegate bool CompareFunc(object obj1, object obj2);上述我們新定義了一個?GetMax?方法,它的返回值為?object?類型,第一個參數為?object?數組類型,第二個參數則是?CompareFunc?委托變量。而?CompareFunc?委托的作用就是對比較規則一個定義,即我們要做的就是傳入對應數組參數的同時也一起傳入響應的比較規則的實現。
定義比較規則:
/*** 比較規則*/ bool CompareInt(object num1, object num2) {return Convert.ToInt32(num1) > Convert.ToInt32(num2); }再看此時我們如何獲取 int 數組的最大值:
[Test] public void Test() {object[] numArr = {32, 445, 65, 321, 4};var max = GetMax(numArr, CompareInt);Console.WriteLine(max);/** 445*/ }而我們如果要獲取一個 string 數組中的最大值,不用做修改,直接傳入 string 類型數組即可:
[Test] public void Test() {object[] numArr = {"32", "445", "65", "321", "4"};var max = GetMax(numArr, CompareInt);Console.WriteLine(max);/** 445*/ }此時來了一個新需求,有如下實體類:
namespace MyTests.Entities {public class User{public User(){}public User(int id, string name, int age){this.id = id;this.name = name;this.age = age;}private int id;private string name;private int age;public int Id{get { return id; }set { id = value; }}public string Name{get { return name; }set { name = value; }}public int Age{get { return age; }set { age = value; }}public override string ToString(){return string.Format("Id: {0}, Name: {1}, Age: {2}", id, name, age);}} } MyTests.Entities.User我們需要定義一個方法能夠返回該實體對象數組中年齡最大的對象,很簡單,我們只需要單獨為 User 的實例定義一個它的比較規則即可,如下:
[Test] public void Test() {object[] userArr ={new User(1, "張三", 34),new User(2, "李四", 23),new User(3, "王五", 34)};var max = GetMax(userArr, CompareUser);Console.WriteLine(max);/** Id: 1, Name: 張三, Age: 34*/ }bool CompareUser(object user1, object user2) {return (user1 as User).Age > (user2 as User).Age; }委托最大的價值在于可以讓我們在編寫代碼時不用考慮委托變量指向哪一個方法,只需要按照聲明委托時的約定傳入參數即可。其實有點類似于接口的作用,我們不需要了解它的具體實現就可以直接使用它。
泛型委托
自定義泛型委托
泛型委托的定義其實與泛型方法的定義相似,格式如下:
delegate <返回值類型> <方法名><泛型名稱1, 泛型名稱2, ...>(參數1, 參數2,...);通過泛型委托上述案例可以修改如下:
using System; using MyTests.Entities; using NUnit.Framework;namespace MyTests {[TestFixture]public class GetMaxNumTest{[Test]public void Test(){User[] userArr ={new User(1, "張三", 34),new User(2, "李四", 23),new User(3, "王五", 34)};var max = GetMax<User>(userArr, CompareUser);Console.WriteLine(max);/** Id: 1, Name: 張三, Age: 34*/}/*** 比較規則*/bool CompareUser(User user1, User user2){return user1.Age > user2.Age;}/*** 泛型方法*/T GetMax<T>(T[] nums, CompareFunc<T> compareFunc){T max = nums[0];for (var i = 1; i < nums.Length; i++){if (compareFunc(nums[i], max))max = nums[i];}return max;}/*** 泛型委托*/delegate bool CompareFunc<T>(T obj1, T obj2);} } MyTests.GetMaxNumTest內置的泛型委托
.Net 中內置兩個泛型委托?Func?和?Action?,日常開發中基本不用自定義委托類型了。?Func?是有返回值的委托,而?Action?是沒有返回值的委托。使用如下:
using System; using MyTests.Entities; using NUnit.Framework;namespace MyTests {[TestFixture]public class Test1{void SayHello(string name){Console.WriteLine("hello " + name);}bool CompareUser(User user1, User user2){return user1.Age > user2.Age;}[Test]public void Test(){// 無返回值的委托Action<string> sayHello = SayHello;sayHello("張三");// 有返回值的委托Func<User, User, bool> compareUser = CompareUser;// 如果是有返回值的委托,那么最后一個泛型參數為返回值類型var isGt = compareUser(new User(1, "張三", 32), new User(2, "李四", 43));Console.WriteLine(isGt);/*hello 張三False*/}} } MyTests.Test而上述的案例也可以修改為如下:
using System; using MyTests.Entities; using NUnit.Framework;namespace MyTests {[TestFixture]public class GetMaxNumTest2{[Test]public void Test(){User[] userArr ={new User(1, "張三", 34),new User(2, "李四", 23),new User(3, "王五", 34)};var max = GetMax<User>(userArr, CompareUser);Console.WriteLine(max);/** Id: 1, Name: 張三, Age: 34*/}/*** 比較規則*/bool CompareUser(User user1, User user2){return user1.Age > user2.Age;}/*** 泛型方法 使用內置泛型委托*/T GetMax<T>(T[] nums, Func<T, T, bool> compareFunc){T max = nums[0];for (var i = 1; i < nums.Length; i++){if (compareFunc(nums[i], max))max = nums[i];}return max;}} } MyTests.GetMaxNumTest匿名方法
匿名方法,就是沒有名字的方法。使用委托的很多時候沒必要定義一個普通方法,因為這個方法只有這個委托會用,并且只用一次,這個時候使用匿名方法最為合適。以將?SayHello?方法的指針賦給委托為例:
using System; using NUnit.Framework;namespace MyTests {[TestFixture]public class Tests{#region 普通方法方式void SayHello(string name){Console.WriteLine("hello " + name);}public void TestOld(){Action<string> sayHello = SayHello;}#endregion#region 匿名方法方式[Test]public void TestNew(){Action<string> sayHello = delegate(string name) { Console.WriteLine("hello " + name); };}#endregion} }將最大值哪個案例使用匿名方法重構后如下:
using System; using MyTests.Entities; using NUnit.Framework;namespace MyTests {[TestFixture]public class GetMaxNumTest2{[Test]public void Test(){User[] userArr ={new User(1, "張三", 34),new User(2, "李四", 23),new User(3, "王五", 34)};// 使用匿名方法var max = GetMax<User>(userArr, delegate(User user1, User user2) { return user1.Age > user2.Age; });Console.WriteLine(max);/** Id: 1, Name: 張三, Age: 34*/}/*** 泛型方法 使用內置泛型委托*/T GetMax<T>(T[] nums, Func<T, T, bool> compareFunc){T max = nums[0];for (var i = 1; i < nums.Length; i++){if (compareFunc(nums[i], max))max = nums[i];}return max;}} } MyTests.GetMaxNumTestlambda表達式
使用
lambda 表達式其實是對匿名方法使用的一個再度簡化,看如下示例:
Action<string> a1 = delegate(string s) { Console.WriteLine(s); };上面是將一個匿名方法指針賦值給一個委托變量,通過 lambda 表達式可簡化如下:
Action<string> a2 = (string s) => { Console.WriteLine(s); };還可以省略參數類型,編譯器會自動根據委托類型推斷:
Action<string> a3 = (s) => { Console.WriteLine(s); };如果只有一個參數,還可以省略小括號:
Action<string> a3 = s => { Console.WriteLine(s); };如果委托有返回值,并且方法體只有一行代碼,這一行代碼還是返回值,那么就可以連方法的大括號和?return?都省略:
Func<int,int,int> a4 = (i, j) => i + j;?=>?可讀作“goes to”。
練習
1、將下面代碼盡可能簡化:
Action<string, bool > a1 = delegate(string s, bool b) {if (b) { Console.WriteLine("true" + s); }else { Console.WriteLine("false" + s); } }; Action<string, bool> a1 = (s, b) => {if (b) Console.WriteLine("true" + s);else Console.WriteLine("false" + s); }; result Func<string, int> f1 = delegate(string str) { return Convert.ToInt32(str);}; Func<string, int> f1 = str => Convert.ToInt32(str); result2、把下面的代碼還原成匿名方法形式:
Action<string, int> a1 = (s, i) => { Console.WriteLine("s=" + s + ",i=" + i); }; Action<string, int> a1 = delegate(string s, int i) { Console.WriteLine("s=" + s + ",i=" + i); }; result Func<int, string> f2 = n => (n + 1).ToString(); Func<int, string> f2 = delegate(int n) {return (n + 1).ToString();}; result Func<int, int> f3 = n => n * 2; Func<int, int> f3 = delegate(int n) { return n * 2; }; result3、寫出下面一個 lambda 表達式的委托類型及非匿名函數形式:
n => n > 0; 委托類型為 Func<int, bool> 非匿名函數形式: public bool IsGtZero(int n) {return n > 0; } result4、使用 lambda 表達式修改獲取最大值案例:
using System; using MyTests.Entities; using NUnit.Framework;namespace MyTests {[TestFixture]public class GetMaxNumTest2{[Test]public void Test(){User[] userArr ={new User(1, "張三", 34),new User(2, "李四", 23),new User(3, "王五", 34)};// 使用匿名方法var max = GetMax<User>(userArr, (User user1, User user2) => user1.Age > user2.Age);Console.WriteLine(max);/** Id: 1, Name: 張三, Age: 34*/}/*** 泛型方法 使用內置泛型委托*/T GetMax<T>(T[] nums, Func<T, T, bool> compareFunc){T max = nums[0];for (var i = 1; i < nums.Length; i++){if (compareFunc(nums[i], max))max = nums[i];}return max;}} } result案例二:擴展集合的Where方法
通過上面學習到的內容,我們可以為集合做一個擴展方法,這個方法的功能是可以根據我們傳入的 lambda 表達式過濾出我們需要的元素集合。
1、編寫擴展方法:
using System; using System.Collections.Generic;namespace MyTests.Ext {/*** 集合擴展方法類*/public static class EnumerableExt{public static IEnumerable<T> MyWhere<T>(this IEnumerable<T> data, Func<T, bool> filter){var list = new List<T>();foreach (var obj in data){if (filter(obj)){list.Add(obj);}}return list;}} } MyTests.Ext.EnumerableExt2、使用:
User[] userArr = {new User(1, "張三", 34),new User(2, "李四", 23),new User(3, "王五", 34) }; // 獲取 name 中包含 "張" 的元素集合 var list = userArr.MyWhere(p => p.Name.Contains("張")); foreach (var user in list)Console.WriteLine(user); /*Id: 1, Name: 張三, Age: 34*/ test委托的組合
委托是可以使用“+”號來進行組合的,組合后會生成一個新的委托對象,調用這個新的委托對象時,會按順序將組合進來的委托依次執行。看如下示例:
using System; using NUnit.Framework;namespace 委托的組合 {[TestFixture]public class Tests{[Test]public void Test1(){Action<string> a1 = SayHello1;Action<string> a2 = SayHello2;Action<string> a3 = SayHello3;// 組合Action<string> a4 = a1 + a2 + a3;a4("張三");/*hello 張三 from SayHello1hello 張三 from SayHello2hello 張三 from SayHello3*/}public void SayHello1(string name){Console.WriteLine("hello " + name + " from SayHello1");}public void SayHello2(string name){Console.WriteLine("hello " + name + " from SayHello2");}public void SayHello3(string name){Console.WriteLine("hello " + name + " from SayHello3");}} } 委托的組合.Tests還可以通過“-”號從委托對象中將已組合進來的委托對象移除,如下:
using System; using NUnit.Framework;namespace 委托的組合 {[TestFixture]public class Tests{[Test]public void Test1(){Action<string> a1 = SayHello1;Action<string> a2 = SayHello2;Action<string> a3 = SayHello3;// 組合Action<string> a4 = a1 + a2 + a3;// 移除 a2 委托對象a4 = a4 - a2;a4("張三");/*hello 張三 from SayHello1hello 張三 from SayHello3*/}public void SayHello1(string name){Console.WriteLine("hello " + name + " from SayHello1");}public void SayHello2(string name){Console.WriteLine("hello " + name + " from SayHello2");}public void SayHello3(string name){Console.WriteLine("hello " + name + " from SayHello3");}} } 委托的組合.Tests委托如果有返回值則有一些特殊,不過委托的組合一般是給事件使用,普通情況很少使用,這里就不再深究。
事件
定義
給委托添加上?event?關鍵字它就是一個事件,格式如下:
[訪問修飾符] event <委托類型> <事件名稱>;案例三:本命年事件
1、修改 User 實體類,定義一個事件,讓其在本命年時觸發:
using System;namespace MyTests.Entities {public class User{public User(){}public User(int id, string name, int age){this.id = id;this.name = name;this.age = age;}private int id;private string name;private int age;public int Id{get { return id; }set { id = value; }}public string Name{get { return name; }set { name = value; }}public int Age{get { return age; }set{if (age % 12 == 0) OnBirthYear(this.name);age = value;}}public override string ToString(){return string.Format("Id: {0}, Name: {1}, Age: {2}", id, name, age);}public event Action<string> OnBirthYear; // 定義一個本命年觸發的事件 } } MyTests.Entities.User2、使用:
var user1 = new User(); var user2 = new User(); var user3 = new User(); // 給每一個 User 對象通過 += 注冊事件 user1.OnBirthYear += (name, age) => { Console.WriteLine(name + age + "歲了,本命年到了,喝稀飯"); }; user2.OnBirthYear += (name, age) => { Console.WriteLine(name + age + "歲了,本命年到了,吃饅頭"); }; user3.OnBirthYear += (name, age) => { Console.WriteLine(name + age + "歲了,本命年到了,大魚大肉"); };user1.Id = 1; user1.Name = "張三"; user1.Age = 23; user2.Id = 2; user2.Name = "李四"; user2.Age = 24;user3.Id = 3; user3.Name = "王五"; user3.Age = 36; /*李四24歲了,本命年到了,吃饅頭 王五36歲了,本命年到了,大魚大肉*/注冊事件觸發的方法時方法的參數及返回值要與事件的委托一致。
事件原理
事件的注冊和移除實際上是通過事件的?add?和?remove?方法完成,在這兩個方法中維護了同一個委托對象,且事件不可為 null。上述實體類也可修改如下:
using System;namespace MyTests.Entities {public class User{public User(){}public User(int id, string name, int age){this.id = id;this.name = name;this.age = age;}private int id;private string name;private int age;public int Id{get { return id; }set { id = value; }}public string Name{get { return name; }set { name = value; }}public int Age{get { return age; }set{age = value;if (age % 12 == 0){if (_OnBirthYear != null){_OnBirthYear(this.name,this.age);}}}}public override string ToString(){return string.Format("Id: {0}, Name: {1}, Age: {2}", id, name, age);}private Action<string, int> _OnBirthYear; // 定義一個本命年觸發的事件public event Action<string, int> OnBirthYear{add { _OnBirthYear += value; }remove { _OnBirthYear -= value; }}} } MyTests.Entities.User委托與事件總結
委托的作用
占位,在不知道將來要執行方法的具體邏輯時,可以先用一個委托變量來代替方法調用(委托的返回值,參數列表要確定)。在實際調用之前,需要為委托賦值。
事件的作用
事件的作用域委托變量一樣,只是在功能上相對委托變量有更多的限制,比如:
- 只能通過 "+=" 或 "-=" 來綁定方法(事件處理程序)。
- 只能在類的內部調用(觸發)事件。
事件和委托的關系
反編譯會發現,事件是由一個私有的委托變量、?add_*?和?remove_*?方法組成,而事件的非簡化寫法就是聲明一個私有的委托變量和??add?、?remove?方法。
相關面試題
1、說一下事件和委托的關系?
網上有很多答案說事件就是委托,這個肯定是錯誤的。只能說事件的實現依賴于委托,因為事件是由一個私有的委托變量、?add_*?和?remove_*?方法組成。
2、接口中可以定義事件嗎?那索引器和屬性呢?
首先,接口中只可以定義方法的簽名,事件、索引器、屬性本質上都是方法,所以是可以定義的。
看如下示例:
using System; using System.Collections.Generic;namespace MyTests {public interface Interface1{// 屬性List<int> list { get; set; }// 索引器long this[int index] { get; set; }// 事件event Action<string, int> OnEvent;} } MyTests.Interface1對應反編譯文件為:
.class public interface abstract auto ansi Interface1 {.custom instance void [mscorlib]System.Reflection.DefaultMemberAttribute::.ctor(string) = { string('Item') }.event [mscorlib]System.Action`2<string, int32> OnEvent{.addon instance void MyTests.Interface1::add_OnEvent(class [mscorlib]System.Action`2<string, int32>).removeon instance void MyTests.Interface1::remove_OnEvent(class [mscorlib]System.Action`2<string, int32>)}.property instance int64 Item{.get instance int64 MyTests.Interface1::get_Item(int32).set instance void MyTests.Interface1::set_Item(int32, int64)}.property instance class [mscorlib]System.Collections.Generic.List`1<int32> list{.get instance class [mscorlib]System.Collections.Generic.List`1<int32> MyTests.Interface1::get_list().set instance void MyTests.Interface1::set_list(class [mscorlib]System.Collections.Generic.List`1<int32>)}} MyTests.Interface1 反編譯 IL 內容可以看到接口中定義事件實際上是聲明了?add_*?和?remove_*?方法的簽名,定義索引器實際上是聲明了?set_Item?和?get_item?方法的簽名,而定義屬性實際上是聲明了?set_*?和?get_*?方法的簽名。
轉載于:https://www.cnblogs.com/zze46/p/10702364.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的C#基础加强(8)之委托和事件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2019-04(1)(Python学习)
- 下一篇: 客来福全屋定制是几线品牌(客来福衣柜的质