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 数组中最大值的方法,很简单如下:
int GetMaxNum(int[] nums) { ]; ; i < nums.Length; i++) { ] > max) max = nums[]; } return max; }
假如又有一个要求,我们定义一个获取 string 数组中最大值(每个 string 变量都可转型为 int 变量)的方法,显示上述方法就不适用了。那有没有什么方法能够让其通用起来呢?
如果我们要获取 string 数组中的最大值,显然我们需要先将其中每个元素转换到 int 类型,然后再进行比较,即重点就是我们要如何定义它的比较规则?
上述代码的比较规则是在第 6 行的 if 块中,我们要做的就是让那个这个 if 块中的内容动态起来,此时委托就派上用场了,看如下代码:
/** * 获取数组中的最大值 */ object GetMax(object[] nums,CompareFunc compareFunc) { ]; ; 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() { , , , , }; var max = GetMax(numArr, CompareInt); Console.WriteLine(max); /* * 445 */ }
而我们如果要获取一个 string 数组中的最大值,不用做修改,直接传入 string 类型数组即可:
[Test] public void Test() { "}; 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 = { , ), , ), , ) }; 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 = { , ), , ), , ) }; 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[]; ; 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; // 如果是有返回值的委托,那么最后一个泛型参数为返回值类型 , ), , )); 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 = { , ), , ), , ) }; 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[]; ; 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 = { , ), , ), , ) }; // 使用匿名方法 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[]; ; i < nums.Length; i++) { if (compareFunc(nums[i], max)) max = nums[i]; } return max; } } }
MyTests.GetMaxNumTest
lambda表达式
使用
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);
result
2、把下面的代码还原成匿名方法形式:
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<).ToString();
Func<int, string> f2 = delegate(int n) { ).ToString(); };
result
Func<;
Func<; };
result
3、写出下面一个 lambda 表达式的委托类型及非匿名函数形式:
n => n > ;
委托类型为 Func<int, bool> 非匿名函数形式: public bool IsGtZero(int n) { ; }
result
4、使用 lambda 表达式修改获取最大值案例:
using System; using MyTests.Entities; using NUnit.Framework; namespace MyTests { [TestFixture] public class GetMaxNumTest2 { [Test] public void Test() { User[] userArr = { , ), , ), , ) }; // 使用匿名方法 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[]; ; 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.EnumerableExt
2、使用:
User[] userArr = { , ), , ), , ) }; // 获取 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 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"); } } }
委托的组合.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 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"); } } }
委托的组合.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 { == ) 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.User
2、使用:
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 = ; user1.Name = "张三"; user1.Age = ; user2.Id = ; user2.Name = "李四"; user2.Age = ; user3.Id = ; user3.Name = "王五"; user3.Age = ; /* 李四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 (_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') } .<string, int32> OnEvent { .addon instance <string, int32>) .removeon instance <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 <int32> list { .<int32> MyTests.Interface1::get_list() .<int32>) } }
MyTests.Interface1 反编译 IL 内容
可以看到接口中定义事件实际上是声明了 add_* 和 remove_* 方法的签名,定义索引器实际上是声明了 set_Item 和 get_item 方法的签名,而定义属性实际上是声明了 set_* 和 get_* 方法的签名。
C#基础加强(8)之委托和事件的更多相关文章
- .NET零基础入门05:委托与事件
一:前言 本小节,我们需要停一停我们的小游戏开发,虽然它现在还不完美,还很简单,甚至还有BUG.但是,为了更好的理解C#,现在到了该深入了解一些基础知识的时候了. 当然,实际上,本小节内容对于零基础入 ...
- C# 基础知识系列- 11 委托和事件
0. 前言 事件和委托是C#中的高级特性,也是C#中很有意思的一部分.出现事件的地方,必然有委托出现:而委托则不一定会有事件出现.那为什么会出现这样的关系呢?这就需要从事件和委托的定义出发,了解其中的 ...
- 黑马程序员-- .net基础加强8之委托,事件,程序集
---------------------- ASP.Net+Android+IOS开发..Net培训.期待与您交流! ---------------------- 一.委托 ============ ...
- .NET基础拾遗(4)委托、事件、反射与特性
Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开 ...
- 【.NET基础】--委托、事件、线程(3)
之前的两篇文章我们了解了委托和事件,本文我们看一下线程. 1,一个窗体程序,默认拥有一个线程(相当于一个商店里面,只有一个店员),这个默认的线程叫做 UI线程/主线程. 2,进程和线程的关系: A,进 ...
- 转载 【.NET基础】--委托、事件、线程(3)
之前的两篇文章我们了解了委托和事件,本文我们看一下线程. 1,一个窗体程序,默认拥有一个线程(相当于一个商店里面,只有一个店员),这个默认的线程叫做 UI线程/主线程. 2,进程和线程的关系: A ...
- 转载 【.NET基础】--委托、事件、线程(2) https://www.cnblogs.com/chengzish/p/4569912.html
[.NET基础]--委托.事件.线程(2) 本文介绍event的使用以及原理,本文接上一篇文章的Demo继续[下载上一篇Demo] 上一篇我们在类(dg_SayHi.cs)里面定义代理了4个Del ...
- 转载 【.NET基础】--委托、事件、线程(1) https://www.cnblogs.com/chengzish/p/4559268.html
[.NET基础]--委托.事件.线程(1) 1,委托 是存放方法的指针的清单,也就是装方法的容器 A, 新建winform项目[01委托],项目中添加dg_SayHi.cs 委托类 用于存储方法 ...
- 【.NET基础】--委托、事件、线程(2)
本文介绍event的使用以及原理,本文接上一篇文章的Demo继续[下载上一篇Demo] 上一篇我们在类(dg_SayHi.cs)里面定义代理了4个Delegate,然后在Button的后台事件中 新建 ...
随机推荐
- BizTalk Map 累积连接字符串
更多内容请查看:BizTalk动手实验系列目录 BizTalk 开发系列 BizTalk 培训/项目开发/技术支持请联系:Email:cbcye ...
- Sublime Text 文件路径补全
最有效和好用的是AutoFileName插件,效果如下: 表格编辑 Table Editor相当好用,安装好后参考自述文件(Preferences --> Package Settings -- ...
- mysql使用问题记录
Mysql Access denied for user 'root' ERROR 1045 (28000): Access denied for user 'root'@'localhost' (u ...
- DirectX using C++_error X3539:ps1_x is no longer supported...解决方案
问题来源 在研究HLSL时编译一个demo出现了error X3539的问题 解决方案 将代码中的ps_1_1 改为ps_2_0 PixelShader = compile ps_1_1 PS(); ...
- 【分享】Web前端开发第三方插件大全
收集整理了一些Web前端开发比较成熟的第三方插件,分享给大家. ******************************************************************** ...
- windows 端口被占用,并杀死进程的方法
netstat -ano | findstr 8081 查询端口 被什么进程占用 tasklist | findstr 2184 根据进程号 查询任务名称 taskkill /f /t /im jav ...
- laravel5.8笔记四:路由
laravel框架,必须先设置路由,才可以访问内部的控制器部分. 路由文件:routes/web.php. 基本路由 Route::get('/user', 'UserController@index ...
- java如何对List集合中的元素进行排序(请收藏)
在java开发中有时候我们需要对List集合中的元素按照一定的规则进行排序,比如说有个Person的集合,我们要根据Person的age属性进行排序输出,这就需要用到Java中提供的对集合进行操作的工 ...
- Nginx 配置参数详解
#定义Nginx运行的用户和用户组 user www www; #nginx进程数,建议设置为等于CPU总核心数. worker_processes 8; #全局错误日志定义类型,[ debu ...
- mysql关联表修改语句
UPDATE tb_irms_trans_pip2optseg a,`tb_irms_trans_pip` b SET a.district=b.district WHERE a.prop_id=b. ...