委托是一种能够声明出指向方法的变量的数据类型。面试
格式: delegate <返回值类型> 委托类型名(参数) ,例如:数组
delegate void MyDel(string str) 。 // 注意:这里除了前面的 delegate 关键字,剩下部分和声明一个函数相同,可是 MyDel 不是函数名,而是委托类型名。
声明委托变量的方式与声明变量相同,都是经过 new 关键字,例:ide
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 张三 */ } } }
委托变量之间能够互相赋值,其实就是一个传递方法指针的过程,如:this
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 方法 /* * 张三 */ } } }
先从一个简单的需求开始,若是咱们须要编写一个获取 int 数组中最大值的方法,很简单以下:spa
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 变量)的方法,显示上述方法就不适用了。那有没有什么方法可以让其通用起来呢?3d
若是咱们要获取 string 数组中的最大值,显然咱们须要先将其中每一个元素转换到 int 类型,而后再进行比较,即重点就是咱们要如何定义它的比较规则?指针
上述代码的比较规则是在第 6 行的 if 块中,咱们要作的就是让那个这个 if 块中的内容动态起来,此时委托就派上用场了,看以下代码:code
/** * 获取数组中的最大值 */ 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); } } }
咱们须要定义一个方法可以返回该实体对象数组中年龄最大的对象,很简单,咱们只须要单独为 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); } }
.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 */ } } }
而上述的案例也能够修改成以下:
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; } } }
匿名方法,就是没有名字的方法。使用委托的不少时候不必定义一个普通方法,由于这个方法只有这个委托会用,而且只用一次,这个时候使用匿名方法最为合适。以将 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; } } }
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”。
一、将下面代码尽量简化:
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); };
Func<string, int> f1 = delegate(string str) { return Convert.ToInt32(str);};
Func<string, int> f1 = str => Convert.ToInt32(str);
二、把下面的代码还原成匿名方法形式:
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); };
Func<int, string> f2 = n => (n + 1).ToString();
Func<int, string> f2 = delegate(int n) { return (n + 1).ToString(); };
Func<int, int> f3 = n => n * 2;
Func<int, int> f3 = delegate(int n) { return n * 2; };
三、写出下面一个 lambda 表达式的委托类型及非匿名函数形式:
n => n > 0;
委托类型为 Func<int, bool> 非匿名函数形式: public bool IsGtZero(int n) { return n > 0; }
四、使用 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; } } }
经过上面学习到的内容,咱们能够为集合作一个扩展方法,这个方法的功能是能够根据咱们传入的 lambda 表达式过滤出咱们须要的元素集合。
一、编写扩展方法:
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; } } }
二、使用:
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 */
委托是可使用“+”号来进行组合的,组合后会生成一个新的委托对象,调用这个新的委托对象时,会按顺序将组合进来的委托依次执行。看以下示例:
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 SayHello1 hello 张三 from SayHello2 hello 张三 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"); } } }
还能够经过“-”号从委托对象中将已组合进来的委托对象移除,以下:
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 SayHello1 hello 张三 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"); } } }
委托若是有返回值则有一些特殊,不过委托的组合通常是给事件使用,普通状况不多使用,这里就再也不深究。
给委托添加上 event 关键字它就是一个事件,格式以下:
[访问修饰符] event <委托类型> <事件名称>;
一、修改 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; // 定义一个本命年触发的事件 } }
二、使用:
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; } } } }
占位,在不知道未来要执行方法的具体逻辑时,能够先用一个委托变量来代替方法调用(委托的返回值,参数列表要肯定)。在实际调用以前,须要为委托赋值。
事件的做用域委托变量同样,只是在功能上相对委托变量有更多的限制,好比:
反编译会发现,事件是由一个私有的委托变量、 add_* 和 remove_* 方法组成,而事件的非简化写法就是声明一个私有的委托变量和 add 、 remove 方法。
一、说一下事件和委托的关系?
网上有不少答案说事件就是委托,这个确定是错误的。只能说事件的实现依赖于委托,由于事件是由一个私有的委托变量、 add_* 和 remove_* 方法组成。
二、接口中能够定义事件吗?那索引器和属性呢?
首先,接口中只能够定义方法的签名,事件、索引器、属性本质上都是方法,因此是能够定义的。
看以下示例:
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; } }
对应反编译文件为:
.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>) } }
能够看到接口中定义事件其实是声明了 add_* 和 remove_* 方法的签名,定义索引器其实是声明了 set_Item 和 get_item 方法的签名,而定义属性其实是声明了 set_* 和 get_* 方法的签名。