1、委托Delegate实质
由一个修饰符+ delegate,跟方法的定义比较类似,也需要声明参数和返回值。声明一个委托,就是声明一种方法签名(参数+返回值),只要是和声明委托方法签名相同的方法,都可以被委托实例托管。
理解:具有相同方法签名的方法(method),他们的调用都可以通过相同方法签名的委托(class)去实现
比如现在有很多排队的需求:排队买奶茶、排队拿号买房。。。,这些需求可让一个排队服务机构去完成,这个机构只需要定义一项委托服务:指派一个人去排队获取xxx
public delegate object WaitGetAccesss(Person person)
还比如快递服务是不是也是类似的场景,快递的本质是把东西从地点A搬运到地点B,同样可以定义public delegate void WaitGetAccesss(object obj,area A,area B);只需要告知要运输的货物、起始地点A、目的地点B即可(这里只针对运输的部分,不包含派送的部分)
几乎所有的快递运输都可以通过这个方式去完成,无论是数码产品、食品这些
自己能做的事情,让这个排队服务机构完成,这不就是现实世界里委托的含义吗?
public delegate void MyFirstDelegate()
委托的实质是一个类,通过反编译得到MyFirstDelegate如下,这个类继承MulticastDelegate,类的内部包含ctor的构造函数,Invoke、BeginInvoke、EndInvoke方法
.class nested public auto ansi sealed MyFirstDelegate extends [System.Runtime]System.MulticastDelegate { // Methods .method public hidebysig specialname rtspecialname instance void .ctor ( object 'object', native int 'method' ) runtime managed { } // end of method MyFirstDelegate::.ctor .method public hidebysig newslot virtual instance int32 Invoke ( int32 x, int32 y ) runtime managed { } // end of method MyFirstDelegate::Invoke .method public hidebysig newslot virtual instance class [System.Runtime]System.IAsyncResult BeginInvoke ( int32 x, int32 y, class [System.Runtime]System.AsyncCallback callback, object 'object' ) runtime managed { } // end of method MyFirstDelegate::BeginInvoke .method public hidebysig newslot virtual instance int32 EndInvoke ( class [System.Runtime]System.IAsyncResult result ) runtime managed { } // end of method MyFirstDelegate::EndInvoke } // end of class MyFirstDelegate
2、委托的定义及执行
定义
委托的参数方法的签名要和委托的方法签名一致(即要有相同的参数及返回值)
public delegate int NumberHandleDelegate(int a,int b); public static int AddNum(int a, int b) { return a+b; } //标准写法 NumberHandleDelegate numberHandleDelegate1 = new NumberHandleDelegate(AddNum); //语法糖,编译器使用的便捷功能,帮忙省略了new NumberHandleDelegate的编写 NumberHandleDelegate numberHandleDelegate2 = AddNum; //lambda表达式(匿名方法) NumberHandleDelegate numberHandleDelegate3 = (int a, int b) => a + b;
执行
{ Student s = new Student(); int num = s.numberHandleDelegate1.Invoke(3, 5); Console.WriteLine($"num:{num}"); //结果打印:num:8 }
3、实际的应用
3.1应用一
public enum PersonType { Student = 1, Teacher = 2 }
现在想针对不同的人有不同的打招呼的方式,有以下两种方案
//方案1:定义枚举,通过枚举判断,分别给出不同的问候方式; public void SayHi(PersonType type) { switch (type) { case PersonType.Student: Console.WriteLine("同学好"); break; case PersonType.Teacher: Console.WriteLine("老师好"); break; default: throw new Exception("没有找到具体类型"); } } //方案2:根据不同的类型的人,分别定义不同的问候方式 public void SayStudentHi() { Console.WriteLine("同学好"); } public void SayTeacherHi() { Console.WriteLine("老师好"); }
优劣分析
//场景1:如果增加一个类型的人 //方案1:多种分支判断 ---如果增加一个类型的人------增加一个校长;---校长好! // 问题:SayHi方法不稳定,一旦增加一个类型的人;SayHi方法就需要修改;方法的所有逻辑都选哟重新测试. 一个方法中有多种业务逻辑---业务逻辑耦合 //方案2:每个方法都式独立的: // 问题:---如果增加一个类型的人---增加一个方法;功能独立,对其他的功能内部不影响; //场景2:如果需要增加一个功能的业务逻辑呢?--打招呼后再握手 //方案1:增加功能的业务逻辑很方便;只需要在方法内加上 Console.WriteLine("握手"); //方案2:增加公共逻辑--每个方法都需要增加--增加的代码都是一样的,----大量的重复代码;
利用委托能覆盖以上两种场景,完美解决以上问题
//定义一个打招呼的委托 public delegate void SayHiDelegate(); //定义一个公共方法,参数为打招呼的委托 public void SayHiPerfect(SayHiDelegate sayHiDelegate) { sayHiDelegate.Invoke(); Console.WriteLine("握手"); }
调用:
//方案三: student.SayHiPerfect(student.SayStudentHi); student.SayHiPerfect(student.SayTeacherHi);
3.2应用二——利用属性和委托实现对方法的包装
public class Teacher { [PassExam] [Register] [PreparaLessons] public void Teach() { Console.WriteLine($"老师教学生上课,核心内容!!!"); } public void show() { Teacher teacher = new Teacher(); Type type = typeof(Teacher); TeachProjectDelegate projectDelegate = new TeachProjectDelegate(teacher.Teach); foreach (var methodInfo in type.GetMethods()) { if (methodInfo.IsDefined(typeof(AbstractAttribute), true)) { foreach (AbstractAttribute attribute in methodInfo.GetCustomAttributes(typeof(AbstractAttribute)).Reverse()) { projectDelegate = attribute.AddPreWork(projectDelegate); } } } projectDelegate.Invoke(); } }
定义的特性AbstractAttribute,及其其他子类PassExamAttribute、PreparaLessonsAttribute、RegisterAttribute
public abstract class AbstractAttribute:Attribute { public abstract TeachProjectDelegate AddPreWork(TeachProjectDelegate teachProjectDelegate); }
public class PassExamAttribute:AbstractAttribute { public override TeachProjectDelegate AddPreWork(TeachProjectDelegate teachProjectDelegate) { TeachProjectDelegate dDelegate = new TeachProjectDelegate(() => { Console.WriteLine("老师要通过课程认证考试"); teachProjectDelegate.Invoke(); Console.WriteLine("课程认证考试成功通过"); }); return dDelegate; } }
public class PreparaLessonsAttribute : AbstractAttribute { public override TeachProjectDelegate AddPreWork(TeachProjectDelegate teachProjectDelegate) { TeachProjectDelegate dDelegate = new TeachProjectDelegate(() => { Console.WriteLine("老师首先得备课》》》"); teachProjectDelegate.Invoke(); Console.WriteLine("老师备课》》》成功"); }); return dDelegate; } }
public class RegisterAttribute:AbstractAttribute { public override TeachProjectDelegate AddPreWork(TeachProjectDelegate teachProjectDelegate) { TeachProjectDelegate dDelegate = new TeachProjectDelegate(() => { Console.WriteLine("注册成为老师"); teachProjectDelegate.Invoke(); Console.WriteLine("注册老师成功"); }); return dDelegate; } }
执行及其结果:
{ Teacher teacher = new Teacher(); teacher.show(); //老师要通过课程认证考试 //注册成为老师 //老师首先得备课》》》 //老师教学生上课,核心内容!!! //老师备课》》》成功 //注册老师成功 //课程认证考试成功通过 }
可以看到经过特性+委托的多层嵌套封装——程序的执行环节自动装配。如果有十个环节,只需要定义10个特性
一个特性就可以增加一个处理环节,这也是ASP.NET core的管道处理模型的思想
四、内置的委托类型——Action、Func、predicate
4.1 Action
Action——无返回值的泛型委托。
Action 表示无参,无返回值的委托
Action<int,string> 表示有传入参数int,string无返回值的委托
Action<int,string,bool> 表示有传入参数int,string,bool无返回值的委托
Action<int,int,int,int> 表示有传入4个int型参数,无返回值的委托
public void Test<T>(Action<T> action,T p) { action(p); }
4.2. Func
Func是有返回值的泛型委托
Func<int> 表示无参,返回值为int的委托
Func<object,string,int> 表示传入参数为object, string 返回值为int的委托
Func<object,string,int> 表示传入参数为object, string 返回值为int的委托
Func<T1,T2,,T3,int> 表示传入参数为T1,T2,,T3(泛型)返回值为int的委托
Func至少0个参数,至多16个参数,根据返回值泛型返回。必须有返回值,不可void
public int Test<T1,T2>(Func<T1,T2,int> func,T1 a,T2 b) { return func(a, b); }
4.3 Predicate
predicate 是返回bool型的泛型委托
predicate<int> 表示传入参数为int 返回bool的委托
Predicate有且只有一个参数,返回值固定为bool
例:public delegate bool Predicate<T> (T obj)
五、多播委托
可以理解成,多件事一起放到委托,一起执行
C#-多播委托 - 闻风听雨 - 博客园 (cnblogs.com)
+=是按照注册的顺序去执行的(顺序)、-=是按照逆序的方式去匹配,匹配到一个取消注册随即结束不会继续向下匹配,如果没有匹配到也不会报错
//+=是按照添加的顺序去执行的(顺序)、-=是按照逆序的方式去匹配,匹配到一个即停止,如果没有匹配到也不会报错 Console.WriteLine("\n"); Console.WriteLine("**********多播委托示例*******"); NoreturnDelegate dDelegate = () => Console.WriteLine("22333"); dDelegate += WriteSomething2; dDelegate += new Teacher().Teach; dDelegate += WriteSomething2; dDelegate.Invoke(); // Console.Read(); Console.WriteLine("\n"); Console.WriteLine("**********多播取消打印*******"); dDelegate -= WriteSomething2; dDelegate.Invoke(); //**********多播委托示例 * ****** //22333 //这里有一些东西 //老师教学生上课, 核心内容!!! //这里有一些东西 //********** 多播取消打印******* //22333 //这里有一些东西 //老师教学生上课, 核心内容!!!
public static void WriteSomething2() { Console.WriteLine("这里有一些东西"); }
public delegate int NoParameterNoReturnDelegate();
六、观察者模式
C#设计模式-观察者模式 - 晓镜水月 - 博客园 (cnblogs.com)
public class Student { public void Package() { Console.WriteLine("学生收拾行李"); } public void DoneWork() { Console.WriteLine("学生准备好作业"); } }
public class School { public void AnnounceTermBegins() { Console.WriteLine("我宣布明天开学了。。。。。"); } /// <summary> /// 我们想按照顺序执行一套程序 /// 学校——宣布开学 /// 妈妈——给孩子买文具、买衣服 /// 学生——做好作业、打包行李 /// 如图这样将这一套宣布开学后的流程封装到学校这个类显然不合理: /// 1、首先违反单一职责原则:妈妈给孩子准备衣服文具、学生做好专业、收拾自己的东西和学校不相干 /// 2、开放封闭原则:后续如果宣布开学了,新增了家长收拾打扮、送孩子上学这些时间,又需要在修改学校开学的这个方法、这显然不合理 /// </summary> //public void TermBegins() //{ // AnnounceTermBegins(); // Mother mother = new Mother(); // Student student = new Student(); // mother.BuyShoes(); // mother.BuyTool(); // student.DoneWork(); // student.Package(); //} ///委托实现观察者模式 public Action TermBeginsAction; public void TermBegins() { AnnounceTermBegins(); TermBeginsAction?.Invoke();//如果TermBeginsAction不为空,就执行开学的方法 } ///事件实现观察者模式 public event Action termBeginEvent ; public void TermBeginsEvent() { termBeginEvent?.Invoke();//如果TermBeginsAction不为空,就执行开学的方法 } }
internal class Mother { public void BuyTool() { Console.WriteLine("家长给孩子买文具"); } public void BuyShoes() { Console.WriteLine("家长给孩子买衣服。。。。。"); } }
//七、观察者模式(行为)帅锅行为,将自身行为引起得其他行为统一封装,引起的得其他行为的增加或删减的逻辑丢到外部,无论引起了什么其他行为,都不改变自身的封装 // { Console.WriteLine("\n"); Console.WriteLine("**********委托实现观察者模式*******"); //缺陷:外部能直接执行委托,例如sc.TermBeginsAction.Invoke();设定的逻辑有不被执行的风险(可能在先宣告开学的时候就把后续的行为做了) School sc = new School(); Mother mother = new Mother(); Student student = new Student(); sc.TermBeginsAction += mother.BuyShoes; sc.TermBeginsAction += mother.BuyTool; sc.TermBeginsAction += student.DoneWork; sc.TermBeginsAction += student.Package; sc.AnnounceTermBegins(); sc.TermBegins();//等价与sc.TermBeginsAction.Invoke(); //sc.TermBeginsAction.Invoke(); //**********委托实现观察者模式 * ****** //我宣布明天开学了。。。。。 //家长给孩子买衣服。。。。。 //家长给孩子买文具 //学生准备好作业 //学生收拾行李 Console.WriteLine("\n"); Console.WriteLine("**********事件实现观察者模式*******"); School sc1 = new School(); Mother mother1 = new Mother(); Student student1 = new Student(); sc1.termBeginEvent += mother1.BuyShoes; sc1.termBeginEvent += mother1.BuyTool; sc1.termBeginEvent += student1.DoneWork; sc1.termBeginEvent += student1.Package; sc1.TermBeginsEvent(); //sc1.termBeginEvent.Invoke();无法执行,因为事件只能在类的内部调用执行,更安全 //只能在类的内部执行,类的子类都不能执行 //**********事件实现观察者模式 * ****** // 家长给孩子买衣服。。。。。 //家长给孩子买文具 // 学生准备好作业 //学生收拾行李 }