C#基础–委托
C#基础–委托
简单说它就是一个能把方法当参数传递的对象,而且还知道怎么调用这个方法,同时也是粒度更小的“接口”(约束了指向方法的签名)
一、什么是委托,委托的本质是什么?
-
跟方法有点类似,有参数,返回值,访问修饰符+ delegate
public delegate void NoReturnNoPara();
-
委托的本质是一个类,继承自一个特殊类
MulticastDelegate
,我们自己在定义类的时候是无法去继承MulticastDelegate
的 -
在类的内部生成一个类
二、委托的实例化,执行委托
-
ILSply反编译–委托的本质其实是一个类
-
委托本质是一个类,这个类的构造函数参数—Method–方法
- 委托可以通过New来实例化,要求传递一个和这个委托的参数和返回值完全匹配的方法【完全匹配】
- 委托有几个参数,方法参数数量也要对应;参数的类型在顺序上也要一一对应
委托
返回参数和方法
返回的参数类型也需要对应
public delegate void NoReturnWithPara(int x, int y);
private static void NoReturnNoParaMehtod()
{
Console.WriteLine("这是一个无参数无返回值的方法。。。");
}
- 委托的实例–可以直接指向一个和这个委托参数+返回值完全匹配的方法
-语法糖–编译器给我们提供额便捷功能–new WithReturnNoPara();省略掉了
WithReturnNoPara withReturnNoPara1 = new WithReturnNoPara(WithReturnNoParaMehtod);
//可以省略
WithReturnNoPara withReturnNoPara2 = WithReturnNoParaMehtod;
- 执行委托实例的Invoke方法–去执行这个委托实例化的指向的这个方法—执行方法
//调用
NoReturnWithPara noReturnWithPara = new NoReturnWithPara(NoReturnNoParaMehtod);
noReturnWithPara.Invoke(55,68);
- 可以执行这个实例内部的三个方法:
//如果委托定义没有参数;在Inovke也没有参数
withReturnNoPara.Invoke();
//开启一个新的线程去执行委托
withReturnNoPara.BeginInvoke(null, null);
//回调
withReturnNoPara.EndInvoke();
- 多种实例化:new、 直接指向一个方法,指向一个Lambad表达式
//new
WithReturnNoPara withReturnNoPara1 = new WithReturnNoPara(WithReturnNoParaMehtod);
//直接指向一个方法
WithReturnNoPara withReturnNoPara2 = WithReturnNoParaMehtod;
//指向一个Lambad表达式
NoReturnWithPara noReturnWithPara3 = (x, y) => { };
三、框架内置委托 Func 和 Action
.NET Framework3.0时代就开始拥有的产物
1. Action
Action是来自于System.RunTime的一个声明好的可以带有一个或者多个参数的委托delegate,没有返回值的委托(最多支持16个入参)
private void NoreturnNopara()
{
}
Action action = new Action(NoreturnNopara);
action.Invoke();
private void DoNothingInt(int i)
{
Console.WriteLine("This is DoNothing");
}
Action<int> action1 = new Action<int>(DoNothingInt);
action.Invoke();
Action<int, List<string>, DateTime, object, int, string, DateTime, object, int, string, DateTime, object, int, string, DateTime, object> action2 = null;
2. Func
-
是来自于System.RunTime的一个声明好的可以有返回值的委托delegate,可以有参数 ,也可以没有参数(最多支持16个入参)
private int ReturnNopara() { return 0; } Func<int> func = new Func<int>(ReturnNopara);
-
如果既有参数 + 返回值,那
<>
中前面类型参数=输入参数,最后的类型参数=返回值Func<int, int> func1 = new Func<int, int>(ToInt); Func<int, string, int> func2 = new Func<int, string, int>(DoNothingIntAndStringNew); Func<int, List<string>, DateTime, object, int, string, DateTime, object, int, string, DateTime, object, int, string, DateTime, object, int> func3 = null;
问题一: 我们自己是可以定义委托的,为什么系统框架要给我们提供这样的两个委托呢?
答案一:【做到委托类型的统一】既然是系统框架给我们定义好了这两个委托,自然是希望我们在以后的开发中都去只是用这两个委托,这样就可以把委托做到统一;我们以后在使用委托的时候,就不用自己再去定义这个委托了;
场景:
//正常
new Thread(new ThreadStart(DoNoting));
//报错
new Thread(new NoReturnNoPara(DoNoting));
//为什么ThreadStart 和 NoReturnNoPara都是没有参数没有返回的委托,但是传入NoReturnNoPara会报错呢?
//因为委托本质是一个类,你传入的实例类型和定义的参数类型不一致,当然会报错啦
//系统框架给我们定义好了这两个委托,自然是希望我们在以后的开发中都去只是用这两个委托
问题二: 那之前定义好的委托呢?
答案二: 【历史包袱】我们是去不掉的,这被称之为历史包袱;
**问题三:**什么情况下可以考虑使用委托?
**答案三:**方法内部业务逻辑耦合严重;如果多个方法中出现了很多重复代码–去除重复代码,逻辑重用,考虑委托,达到逻辑解耦的目的。
- 从上而下:代码稳定,不需要修改;执行的逻辑不在同一个方法中–逻辑解耦
- 从下而上:去掉了重复代码,逻辑重用
四、委托的嵌套使用(俄罗斯套娃)
Asp .Net Core的核心设计,委托实现中间件的应用;
俄罗斯套娃,属于番外装饰器–AOP支持;就是把委托这个箱子一层一层的包装起来,将要执行的核心业务逻辑封装在最内部的箱子中,执行顺序是从最外层开始到最内层,在一层一层执行出来。
每一层执行的时候,我们就可以增加一些其他的业务逻辑;-- AOP的支持,装饰器
经过特性
+委托
多层嵌套的封装–程序的执行环节的自动装配;如果有十个环节,那么只需要定义10个特性,分别标记就可以增加一个处理环节–ASP.NET Core的管道处理模型。
普通类 InvokerAction.cs – Method方法
public class InvokerAction
{
[BeforeWriteLogAttribute]
[BeforeMethodAttribute]
public void Method(int i)
{
Console.WriteLine("这里是要执行的核心业务逻辑");
}
}
普通方式:
InvokerAction invokerAction = new InvokerAction();
invokerAction.Method(256);
反射:
InvokerAction invokerAction = new InvokerAction();
Type type = invokerAction.GetType();
MethodInfo methodInfo = type.GetMethod("Method");
methodInfo.Invoke(invokerAction, new object[] { 123 });
反射+委托:
public delegate void ShowDelegate();
public static void Show()
{
InvokerAction invokerAction = new InvokerAction();
Type type = invokerAction.GetType();
MethodInfo methodInfo = type.GetMethod("Method");
//实例化一个委托:委托内部包含了一个行为: 行为:执行Method方法
ShowDelegate showMethod = new ShowDelegate(() =>
{
methodInfo.Invoke(invokerAction, new object[] { 123 });
});
showMethod.Invoke();
}
套娃–多层委托的嵌套:
ShowDelegate showmthod1 = new ShowDelegate(() =>
{
showMthod.Invoke();
});
ShowDelegate showmthod2 = new ShowDelegate(() =>
{
showmthod1.Invoke();
});
ShowDelegate showmthod3 = new ShowDelegate(() =>
{
showmthod2.Invoke();
});
ShowDelegate showmthod4 = new ShowDelegate(() =>
{
showmthod3.Invoke();
});
//执行
showmthod4.Invoke();
定义一个特性:在特性中定义每个环节中要执行的动作:
抽象类–AbstractMethodAttribute.cs
public abstract class AbstractMethodAttribute : Attribute
{
/// <summary>
/// 在xxx 执行之前执行的点业务落
/// </summary>
public abstract ShowDelegate Do(ShowDelegate action);
}
BeforeWriteLogAttribute.cs
public class BeforeWriteLogAttribute : AbstractMethodAttribute
{
/// <summary>
/// 在xxx 执行之前执行的点业务落
/// </summary>
public override ShowDelegate Do(ShowDelegate action)
{
ShowDelegate actionResult = new ShowDelegate(() =>
{
{
Console.WriteLine("前面写一个日志"); //这里可以随便写。。。。
}
action.Invoke();
{
Console.WriteLine("后面写一个日志"); //这里可以随便写。。。。
}
});
return actionResult;
}
}
BeforeMethodAttribute.cs
public class BeforeMethodAttribute : AbstractMethodAttribute
{
/// <summary>
/// 在xxx 执行之前执行的点业务落
/// </summary>
public override ShowDelegate Do(ShowDelegate action)
{
ShowDelegate actionResult = new ShowDelegate(() =>
{
{
Console.WriteLine("在xxx 执行之前执行点业务逻辑"); //这里可以随便写。。。。
}
action.Invoke();
{
Console.WriteLine("在xxx 执行之后执行点业务逻辑"); //这里可以随便写。。。。
}
});
//actionResult.Invoke();
return actionResult;
}
}
调用:
public static void Show()
{
InvokerAction invokerAction = new InvokerAction();
Type type = invokerAction.GetType();
MethodInfo methodInfo = type.GetMethod("Method");
ShowDelegate showMethod = new ShowDelegate(() =>
{
methodInfo.Invoke(invokerAction, new object[] { 123 });
});
//如果方法上存在继承于自AbstractMethodAttribute的属性
if (methodInfo.IsDefined(typeof(AbstractMethodAttribute), true))
{
//获取所有属性
foreach (AbstractMethodAttribute attribute in methodInfo.GetCustomAttributes().Reverse())
{
//第一次循环时,showMethod.Invoke 交于第一个属性执行
//接下来得每次执行,都是下一个属性执行上一个属性返回来得委托
//最后形成一个俄罗斯套娃,值得注意的时候,这时候所有委托并没有实际执行
showMethod = attribute.Do(showMethod);
}
}
//返回的最后一个委托开始执行,即从套娃的最外层开始执行,最终到 showMethod.Invoke -> methodInfo.Invoke;
showMethod.Invoke();
}
五、多播委托
-
多播委托:我们声明的任何一个委托都是多播委托;任何一个委托都是继承自MulticastDelegate(多播委托),定义的所有的委托都是多播委托;
-
作用:可以通过+= 把多个方法添加到这个委托中去;形成一个方法的执行链;利用
action1.Invoke()
执行委托的时候,按照添加方法的顺序,依次去执行委托;Action action = new Action(DoNothing); action += DoNothingStatic; action += new Student().Study; action += Student.StudyAdvanced; action += DoNothing; action += () => { Console.WriteLine("this is 拉姆达表达式。。。"); };
-
action.BeginInvoke(); //开启一个新的线程 去执行委托
注册有多个方法的委托,不能使用BeginInvoke;如上述action, 因添加多个方法,所以不能使用 BeginInvoke直接执行,需要用到第四点的
action.GetInvocationList()
获取委托列表循环执行; -
想要开启一个新的线程去执行委托呢?
通过action.GetInvocationList() 获取到所有的委托,然后循环,每个方法执行的时候可以BeginInvoke
foreach (Action action1 in action.GetInvocationList()) { //action1.Invoke(); action1.BeginInvoke(null,null); }
-
多播委托也可以通过-=移除方法
是从后往前,逐个匹配,如果匹配不到,就不做任何操作就不做任何操作;如果匹配到,就把当前这个移除,且停止去继续往后匹配;
action -= DoNothing;
-
在移除的方法的时候,必须是同一个实例,同一个方法才能移除;
action -= new Student().Study; //没有移除掉:因为不是同一个实例
-
lambda表示式是不能移除的;
虽然方法体内容是一样的,但是lambada表示在底层会生成不同的方法
action -= () => //没有移除: 其实是因为不是同一个方法---lambda表达式--在底层会生成不同的方法 { Console.WriteLine("this is 拉姆达表达式。。。"); };
六、观察者模式
事例:猫的故事
Cat.cs
public void Miao()
{
Console.WriteLine("{0} Miao", this.GetType().Name);
new Dog().Wang(); //狗叫了
new Mouse().Run();//老鼠跑了
new Baby().Cry(); // 小孩哭了
}
缺点:
- 职责不单一 — 一个类中包含的职责太多了,除了Miao 一声,依赖于其他的类太多;
- 依赖太重–依赖于Dog、Mouse、…—代码不稳定;Dog、Mouse、…任何一个类的修改,都有可能会影响到这只猫;
- 这完全是面向过程式编程–完全是翻译需求;
改造目的:
- 职责单一: 猫的职责仅仅只是Miao一下,其他的动作,不是猫的动作;
- 猫Miao一声后会引发一些列的动作,应该放出去,不能属于这只猫
- 猫Miao一声后,触发一些列的动作—不能把这一些列的动作放在猫的内部
接口类 IObject.cs:
public interface IObject
{
void DoAction();
}
观察者们: 【订阅者】
public class Dog : IObject
{
public void DoAction()
{
this.Wang();
}
public void Wang()
{
Console.WriteLine("{0} Wang", this.GetType().Name);
}
}
public class Mouse : IObject
{
public void DoAction()
{
this.Run();
}
public void Run()
{
Console.WriteLine("{0} Run", this.GetType().Name);
}
}
public class Baby : IObject
{
public void DoAction()
{
this.Cry();
}
public void Cry()
{
Console.WriteLine("{0} Cry", this.GetType().Name);
}
}
发布者1:Cat.cs 【委托】
- 通过定义委托属性
- 给委托+=方法–Miao一声之后,要触发的动作
- 特点:把要执行的动作不放在猫的内部从外面去指定
- 多播委托+=来完成了一系列动作的转移,把猫Miao一声后的动作转移到上端
- 去掉了猫对于其他的依赖—提高了猫的稳定性
- 通过委托实现了观察者模式–观察者模式—把不属于我的动作转移出去—甩锅
public Action MiaoAction = null;
/// <summary>
/// 这个方法仅仅只是Miao一声
/// 引发的动作---可以放到多播委托中去
/// </summary>
public void MiaoDelegate()
{
Console.WriteLine("{0} MiaoDelegate", this.GetType().Name);
MiaoAction?.Invoke();//?. 如果不为null,就执行后面的动作; 若event不为null,则invoke,这是C#6的新语法。 ?.称为空值传播运算符。
}
cat.MiaoAction += new Dog().Wang; //狗叫了
cat.MiaoAction += new Mouse().Run;//老鼠跑了
cat.MiaoAction += new Mother().Wispher;
cat.MiaoAction += new Baby().Cry; // 小孩哭了
cat.MiaoDelegate(); //执行1
cat.MiaoAction(); //和执行1一样,也可以执行
发布者2:Cat.cs 【接口 + 列表 + 循环执行】
public List<IObject> observerlist = new List<IObject>(); //IObject 含有 DoAction方法
public void MiaoObsever()
{
Console.WriteLine("{0} MiaoObsever", this.GetType().Name);
if (observerlist.Count>0)
{
foreach (var item in observerlist)
{
item.DoAction();
}
}
}
cat.observerlist.Add(new Dog()); //狗叫了
cat.observerlist.Add(new Mouse());//老鼠跑了
cat.observerlist.Add(new Baby()); // 小孩哭了
cat.MiaoObsever();
两者从本质上来说是没有什么太大的区别
第一种是通过多播委托来支持,是语法层面来支持;
第二种是通过面向对象的方法来完成,是程序设计层面来支持;
七、事件
发布者3:Cat.cs 【事件】
public event Action MiaoEventHanlder = null;
public void MiaoEnvent()
{
Console.WriteLine("{0} MiaoEnvent", this.GetType().Name);
MiaoEventHanlder?.Invoke();//若event不为null,则invoke,这是C#6的新语法。 ?.称为空值传播运算符。
}
cat.MiaoEventHanlder += new Dog().Wang; //狗叫了
cat.MiaoEventHanlder += new Mouse().Run;//老鼠跑了
cat.MiaoEventHanlder += new Mother().Wispher;
cat.MiaoEnvent();
cat.MiaoEventHanlder.Invoke(); //不能执行
问题一:委托和事件有什么联系和区别?
-
事件是委托的实例,事件是特殊的委托;
-
事件和委托都可以使用
+=
和-=
;
问题二:既然有了多播委托,为什么又搞个事件呢?
安全
- 事件是只能在类的内部执行,就算是你的子列也不能执行当前类中的事件,只能由自己来执行
- 事件从外面操作,只能有一个
+=
/-=