.NET\C#进阶----委托、多播委托、事件

631 阅读16分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

建议打开文章目录进行观看,观看时按照代码示例调试、执行方便理解

一、什么是委托

1.委托跟方法类似,有参数、有返回值、有修饰符、+delegate。
2.在反编译中,我们所定义的委托在CustomDelegate中都有对应的calss,所以委托的本质:委托类----继承自System.MulticastDelegate的类(MulticastDelegate是一个继承自delegate的特殊的抽象类,自定义类的时候无法主动继承该类,MulticastDelegate类的内部包含有构造函数+方法)

我自己的感受:委托就像是一个自动贩卖机,方法是里面的商品,方法名是商品对应的序号,只要你输入对了序号就能定位到正确的商品。


二、委托的实例化与使用

委托可以通过new来实例化使用,要求必须传递一个和这个委托的参数和返回值完全匹配的方法
在Invoke()时需要根据委托的定义来进行传参

下面例子里使用到的委托类:

/// <summary>
/// 1.无参数无返回值委托
/// </summary>
public delegate void NoReturnNoParameter1();
public class DelegateClass
{
	/// <summary>
	/// 2.无参数无返回值委托
	/// </summary>
	public delegate void NoReturnNoParameter2();

	/// <summary>
	/// 3.有参数无返回值委托
	/// </summary>
	public delegate void NoReturn(int x, int y);

	/// <summary>
	/// 4.无参数有返回值委托
	/// </summary>
	/// <returns></returns>
	public delegate int NoParameter();

	/// <summary>
	/// 5.有参数有返回值委托
	/// </summary>
	/// <returns></returns>
	public delegate int ReturnAndParameter(out int x, ref int y);

	/// <summary>
	/// 1.2无参数无返回值的方法
	/// </summary>
	public void NoReturnNoParameterMethod()
	{
		Console.WriteLine("这是一个无参数无返回值的方法");
	}

	/// <summary>
	/// 3.有参数无返回值的方法
	/// </summary>
	public void NoReturnMethod(int x, int y)
	{
		Console.WriteLine("这是一个有参数无返回值的方法:x={0},y={1}", x, y);
	}

	/// <summary>
	/// 4.无参数有返回值的方法
	/// </summary>
	/// <param name="x"></param>
	/// <param name="y"></param>
	public int NoParameterMethod()
	{
		Console.WriteLine("这是一个无参数有返回值的方法");
		return 1;
	}

	/// <summary>
	/// 5.有参数有返回值的方法
	/// </summary>
	/// <param name="x"></param>
	/// <param name="y"></param>
	/// <returns></returns>
	public int ReturnAndParameterMethod(out int x, ref int y)
	{
		x = 1;
		Console.WriteLine("这是一个有参数有返回值的方法:x={0},y={1}", x, y);
		return y;
	}
}

在上面的委托类中增加一个示例方法Show():

/// <summary>
/// 委托实例化使用示例
/// 上面的代码中,我们定义了一个委托类型的变量,并且将一个方法作为委托的实例化,
/// 委托的有无参数、返回值,要与方法的有无参数、返回值保持一致。
/// </summary>
public void Show()
{
	//===================================多种实例化==================================================
	//第一种方法(委托实例化后传入返回值、参数完全相同的方法的方法名)
	NoReturnNoParameter1 delegate1 = new NoReturnNoParameter1(NoReturnNoParameterMethod);
	NoReturnNoParameter2 delegate2 = new NoReturnNoParameter2(NoReturnNoParameterMethod);
	NoReturn delegate3 = new NoReturn(NoReturnMethod);
	NoParameter delegate4 = new NoParameter(NoParameterMethod);
	ReturnAndParameter deletegate5 = new ReturnAndParameter(ReturnAndParameterMethod);

	//第二种方法(直接指向返回值、参数完全相同的方法)----编译器提供的语法糖
	NoReturnNoParameter1 delegate11 = NoReturnNoParameterMethod;
	NoReturnNoParameter2 delegate22 = NoReturnNoParameterMethod;
	NoReturn delegate33 = NoReturnMethod;
	NoParameter delegate44 = NoParameterMethod;
	ReturnAndParameter deletegate55 = ReturnAndParameterMethod;

	//第三种方式(直接指向Lambda表达式) 
	NoReturnNoParameter1 delegate111 = () => { };
	NoReturnNoParameter2 delegate222 = () => { };
	NoReturn delegate333 = ( x, y ) => { };
	NoParameter delegate444 = () => 1;
	ReturnAndParameter deletegate555 = (out int x, ref int y) => { x = 1; y = 2; return y; };

	//===================================执行委托的三个方法=======================================
	delegate1.Invoke();
	delegate2.Invoke();
	delegate3.Invoke(1, 2);
	int num1 = delegate4.Invoke();
	int y = 2;
	int num2= deletegate5.Invoke(out num2, ref y);

	//开启线程执行委托
	//delegate1.BeginInvoke(null, null);
	//回调
	//delegate1.EndInvoke(null);
}

调用方法测试:

static void Main(string[] args)
{
	DelegateClass delegateClass = new DelegateClass();
	delegateClass.Show();
}

执行结果: 在这里插入图片描述

三、委托的作用与意义

委托的优势:
1.代码更稳定,中控方法不需要修改
2.多个情况需要的执行逻辑不在同一个方法中,减少了局部改动测试整体的需要。--逻辑解耦
3.从上而下减少了重复代码,公共逻辑宠用
使用委托的场景建议:
1.方法内部业务逻辑耦合严重
2.如果多个方法,有很多重复代码---去掉重复代码--逻辑重用


需求场景:需要满足多条件多方案并且后续会有公共业务逻辑增加
比如:中国人讲中文,美国人讲英语,日本人讲日语
后期需求:
增加公共逻辑:说话前要举手

正常做法会是:
1.在同一个方法中根据传递进来的标识符进行判断
----缺点:如果变更需求就需要更改原方法,可能会导致整个方法进行重新测试。
----优点:增加公共逻辑方便
2.写多个方法,然后按需调用
----优点:相比第一个方法,只需要新增一个独立的方法,对其他的功能不会造成影响
----缺点:如果需要增加公共逻辑需要每个方法都进行重复代码的新增
上面两个方法优缺点都很明显,委托可以完美的将上面两个方案的优点结合避免缺点出现(既没有大量重复代码,也相对稳定),便于后期维护。


代码:

public class PeopleClass
{
	/// <summary>
	/// 姓名
	/// </summary>
	public string Name { get; set; }

	/// <summary>
	/// 国家
	/// </summary>
	public string Country { get; set; }

	/// <summary>
	/// 中国的人语言
	/// </summary>
	public void Chinese()
	{
		Console.WriteLine("说中文");
	}

	/// <summary>
	/// 美国人的语言
	/// </summary>
	public void Americans()
	{
		Console.WriteLine("Say English");
	}

	/// <summary>
	/// 日本人的语言
	/// </summary>
	public void Japanese()
	{
		Console.WriteLine("は日本語を話す");
	}
	
	//=====================================================这是增加公共逻辑前后的分割线======================================================================
	
	/// <summary>
	/// 定义一个委托
	/// </summary>	
	public delegate void PeopleLanguage();
	
	/// <summary>
	/// 声明一个传递委托的方法,方便共用新增的通用逻辑
	/// </summary>
	/// <param name="language"></param>
	public void PeopleMethod(PeopleLanguage language)
	{
		//新增的公共逻辑:说话前要举手
		Console.WriteLine("举手!");
		language.Invoke();
	}

}

调用方法:

static void Main(string[] args)
{
	PeopleClass peopleClass = new PeopleClass();
	//中国人说话
	PeopleLanguage peopleLanguage = new PeopleLanguage(peopleClass.Chinese);
	peopleClass.PeopleMethod(peopleLanguage);

	//美国人说话
	PeopleLanguage peopleLanguage2 = new PeopleLanguage(peopleClass.Americans);
	peopleClass.PeopleMethod(peopleLanguage2);

	//日本人说话
	PeopleLanguage peopleLanguage3 = new PeopleLanguage(peopleClass.Japanese);
	peopleClass.PeopleMethod(peopleLanguage3);
}

结果: 在这里插入图片描述

四、俄罗斯套娃----ASP.NET CORE的核心思想(委托的嵌套使用)

把委托这个箱子,一层一层的包包装,在每一次执行的时候,增加一些业务逻辑.要执行的核心业务逻辑是在最内部的这个箱子中,执行顺序是从最外层执行到最内层一层一层的执行出来。
引用场景:AOP;装饰器

委托嵌套Demo(1)----简易版:
示意图:
在这里插入图片描述

public class DelegateNestedClass
{
	//第一步 声明一个委托
	public delegate void MyNameIsDelegate();
	public static void ThisIsClass()
	{
		//第二步 实例化类
		MyNameIsClass myNameIsClass = new MyNameIsClass();

		//第三步 通过反射获取类中的方法
		Type type = myNameIsClass.GetType();
		MethodInfo methodInfo = type.GetMethod("MyNameIsMethod");

		//第四步 实例化委托→委托中存在行为→行为执行方法
		MyNameIsDelegate myNameIsDelegate = new MyNameIsDelegate(() => {
			methodInfo.Invoke(myNameIsClass, null);
		});

		//第五步 套娃开始
		//1.第一层套娃
		MyNameIsDelegate myNameIsDelegate1 = new MyNameIsDelegate(() => {
			myNameIsDelegate.Invoke();
		});
		
		//2.第二层套娃
		MyNameIsDelegate myNameIsDelegate2 = new MyNameIsDelegate(() => {
			myNameIsDelegate1.Invoke();
		});

		//第六步 执行委托
		myNameIsDelegate2.Invoke();
	}

}

/// <summary>
/// 我是一个类
/// </summary>
public class MyNameIsClass
{
	/// <summary>
	/// 我是一个方法
	/// </summary>
	public void MyNameIsMethod()
	{
		Console.WriteLine("我是一个方法");
	}
}

委托嵌套Demo(2)----进阶版(特性):

可以自由的在执行Method方法的前后增加业务逻辑,如下图所示: 在这里插入图片描述

public delegate void MyNameIsDelegate();
public static void ThisIsClass()
{
	MyNameIsClass myNameIsClass = new MyNameIsClass();
	
	Type type = myNameIsClass.GetType();
	MethodInfo methodInfo = type.GetMethod("MyNameIsMethod");
	
	MyNameIsDelegate myDelegate= new MyNameIsDelegate(() =>
	 {
		 methodInfo.Invoke(myNameIsClass, null);
	 });
	
	if (methodInfo.IsDefined(typeof(MethodAttribute), true))
	{
		foreach (MethodAttribute methodAttribute in methodInfo.GetCustomAttributes())
		{
			myDelegate = methodAttribute.Do(myDelegate);
		}
	}

	myDelegate.Invoke();
}

public abstract class MethodAttribute: Attribute
{
	public abstract MyNameIsDelegate Do(MyNameIsDelegate myNameIsDelegate);
}

/// <summary>
/// 定义一个特性,特性中存在一个以委托为方法
/// </summary>
public class LogAttribute : MethodAttribute
{
	public override MyNameIsDelegate Do(MyNameIsDelegate myNameIsDelegate)
	{
		MyNameIsDelegate myDelegate=new MyNameIsDelegate(()=>{
			Console.WriteLine("LogAttribute在执行委托之前输出了一条日志");
			myNameIsDelegate.Invoke();
			Console.WriteLine("LogAttribute在执行委托之后输出了一条日志");
		});
		return myDelegate;
	}
}

/// <summary>
/// 又定义一个特性,特性中存在一个以委托为方法
/// </summary>
public class LogAgainAttribute : MethodAttribute
{
	public override MyNameIsDelegate Do(MyNameIsDelegate myNameIsDelegate)
	{
		MyNameIsDelegate myDelegate = new MyNameIsDelegate(() => {
			Console.WriteLine("LogAgainAttribute在执行委托之前输出了一条日志");
			myNameIsDelegate.Invoke();
			Console.WriteLine("LogAgainAttribute在执行委托之后输出了一条日志");
		});
		return myDelegate;
	}
}

使用到的类:

/// <summary>
/// 我是一个类
/// </summary>
public class MyNameIsClass
{
	/// <summary>
	/// 我是一个方法
	/// </summary>
	[Log]
	[LogAgain]
	public void MyNameIsMethod()
	{
		Console.WriteLine("我是一个方法");
	}
}

执行结果: 在这里插入图片描述

五、框架内置委托Action/Func----建议使用委托的时候直接使用Action/Func

Action:来自于System.RunTime的一个声明好无返回值(可带有参数)的delegate
Func来自于System.RunTime的一个声明好有返回值(可带有参数)的delegate
----尖括号中前面类型参数:输入参数(最多支持16个),最后面的类型参数是作为返回值
Action和Func存在的意义:

委托的本质就是类,定义多个委托,其实就是新增了多个类。使用Action/Func便不需要我们每次都去新增委托,可以把委托做到统一。

/// <summary>
/// 1.无参数无返回值的方法
/// </summary>
public void NoReturnNoParameterMethod()
{
	Console.WriteLine("这是一个无参数无返回值的方法");
}

/// <summary>
/// 2.有参数无返回值的方法
/// </summary>
public void NoReturnMethod(int x, int y)
{
	Console.WriteLine("这是一个有参数无返回值的方法:x={0},y={1}", x, y);
}

/// <summary>
/// 3.无参数有返回值的方法
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
public int NoParameterMethod()
{
	Console.WriteLine("这是一个无参数有返回值的方法");
	return 1;
}

/// <summary>
/// 4.有参数有返回值的方法
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public int ReturnAndParameterMethod(int x, string y)
{
	Console.WriteLine("这是一个有参数有返回值的方法:x={0},y={1}", x, y);
	return x;
}

public void show()
{
	//无返回值无参数Action示例
	Action action = new Action(NoReturnNoParameterMethod);
	action.Invoke();

	//无返回值有参数Action示例
	Action<int, int> action2 = new Action<int, int>(NoReturnMethod);
	action2.Invoke(1, 2);

	//有返回值无参数Action示例
	Func<int> func = new Func<int>(NoParameterMethod);
	func.Invoke();

	//有返回值有参数Action示例
	Func<int, string, int> func2 = new Func<int, string, int>(ReturnAndParameterMethod);
	func2.Invoke(4, "Hello Word");
}

打破最多入参16个的限制(虽然应该是用不到的):

public class FrameWorkDelegate
{
	public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16, in T17,in T18>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16, T17 arg17,T18 arg18);

	public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16, in T17, in T18,out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16, T17 arg17, T18 arg18);

	public void show()
	{
		//传入18个参数
		Action<int, string, int, string, int, string, int, string, int, string, int, string, int, string, int, string, int, int> action = null;
		//传入18个参数
		Func<int, string, int, string, int, string, int, string, int, string, int, string, int, string, int, string, int, int, int> func = null;
	}
}

六、多播委托

可以通过+= 把多个方法添加到这个委托中去,执行委托的时候,按照添加方法的顺序依次去执行委托。 可以通过-=移除方法---是从后往前,逐个匹配,如果匹配不到,就不做任何操作就不做任何操作;如果匹配到,就把当前这个移除,且停止去继续往后匹配。
----实例中的方法无法移除,比如示例中的:new Student().NoReturnNoParameterMethodThree,因为此处相当于每次都声明一个新的实例,所以此处与上面的并不是同一个实例导致移除无效。
----lambda表达式也无法移除,因为lambada表达式是在底层会生成一个方法。

1.增加方法(+=)

额外用到的类:

public class Student
{
	public void NoReturnNoParameterMethodThree()
	{
		Console.WriteLine("第三个执行的方法");
	}

	public static void NoReturnNoParameterMethodFour()
	{
		Console.WriteLine("第四个执行的方法");
	}

	public void NoReturnMethodThree(int x, string y)
	{
		Console.WriteLine($"第三个执行的方法:x={x},y={y}");
	}

	public static void NoReturnMethodFour(int x, string y)
	{
		Console.WriteLine($"第四个执行的方法:x={x},y={y}");
	}

	public int NoParameterMethodThree()
	{
		Console.WriteLine("第三个执行的方法");
		return 1;
	}

	public static int NoParameterMethodFour()
	{
		Console.WriteLine("第四个执行的方法");
		return 1;
	}

	public int ReturnAndParameterMethodThree(int x, string y)
	{
		Console.WriteLine("第三个执行的有参数有返回值的方法:x={0},y={1}", x, y);
		return x;
	}

	public static int ReturnAndParameterMethodFour(int x, string y)
	{
		Console.WriteLine("第四个执行的有参数有返回值的方法:x={0},y={1}", x, y);
		return x;
	}
}

示例类:

public class FrameWorkDelegate
{
	/// <summary>
	/// 1.无参数无返回值的方法
	/// </summary>
	public void NoReturnNoParameterMethod()
	{
		Console.WriteLine("第一、五次执行的无参数无返回值的方法");
	}

	public void NoReturnNoParameterMethod2()
	{
		Console.WriteLine("第二次执行的无参数无返回值的方法");
	}

	/// <summary>
	/// 2.有参数无返回值的方法
	/// </summary>
	public void NoReturnMethod(int x, string y)
	{
		Console.WriteLine($"第一、五次执行的参数无返回值的方法:x={x},y={y}");
	}

	public void NoReturnMethod2(int x, string y)
	{
		Console.WriteLine($"第二次执行的有参数无返回值的方法:x={x},y={y}");
	}
	/// <summary>
	/// 3.无参数有返回值的方法
	/// </summary>
	/// <param name="x"></param>
	/// <param name="y"></param>
	public int NoParameterMethod()
	{
		Console.WriteLine("第一、五次执行的无参数有返回值的方法");
		return 1;
	}

	public int NoParameterMethod2()
	{
		Console.WriteLine("第二次执行的无参数有返回值的方法");
		return 1;
	}

	/// <summary>
	/// 4.有参数有返回值的方法
	/// </summary>
	/// <param name="x"></param>
	/// <param name="y"></param>
	/// <returns></returns>
	public int ReturnAndParameterMethod(int x, string y)
	{
		Console.WriteLine("第一、五次执行的有参数有返回值的方法:x={0},y={1}", x, y);
		return x;
	}
	public int ReturnAndParameterMethod2(int x, string y)
	{
		Console.WriteLine("第二次执行的有参数有返回值的方法:x={0},y={1}", x, y);
		return x;
	}

	public void show()
	{
		Console.WriteLine("============无参无返回值示例============");
		Action action1 = new Action(NoReturnNoParameterMethod);
		action1 += NoReturnNoParameterMethod2;
		//Student类中的一个无参无返回值的方法
		action1 += new Student().NoReturnNoParameterMethodThree;
		//Student类中的一个无参无返回值的静态方法
		action1 += Student.NoReturnNoParameterMethodFour;
		//可重复添加同一个方法
		action1 += NoReturnNoParameterMethod;
		action1 += () =>
		{
			Console.WriteLine("这是lambda表达式");
		};
		action1.Invoke();

		//多播委托开启线程执行
		foreach (Action action in action1.GetInvocationList())
		{
			//不知道是不是.net版本升级的问题,BeginInvoke使用一直报错,但是一直到不到报错原因,只能换位Task.Run()
			//action.BeginInvoke(null, null);
			Task.Run(() => action.Invoke());
		}

		Console.WriteLine("============有参无返回值示例============");
		Action<int, string> action2 = new Action<int, string>(NoReturnMethod);
		action2 += NoReturnMethod2;
		action2 += new Student().NoReturnMethodThree;
		action2 += Student.NoReturnMethodFour;
		action2 += NoReturnMethod;
		action2 += (x, y) =>
		{
			Console.WriteLine($"这是lambda表达式{x},{y}");
		};
		action2.Invoke(1, "2");


		//多播委托开启线程执行
		foreach (Action<int, string> action in action2.GetInvocationList())
		{
			//action.BeginInvoke(1,"2",null, null);
			Task.Run(() => action.Invoke(1, "2"));
		}

		Console.WriteLine("============无参有返回值示例============");
		Func<int> func1 = new Func<int>(NoParameterMethod);
		func1 += NoParameterMethod2;
		func1 += new Student().NoParameterMethodThree;
		func1 += Student.NoParameterMethodFour;
		func1 += NoParameterMethod;
		func1 += () =>
		{
			Console.WriteLine("这是lambda表达式");
			return 1;
		};
		func1.Invoke();

		//多播委托开启线程执行
		foreach (Func<int> func in func1.GetInvocationList())
		{
			//func.BeginInvoke(null, null);
			Task.Run(() => func.Invoke());
		}

		Console.WriteLine("============有参有返回值示例============");

		Func<int, string, int> func2 = new Func<int, string, int>(ReturnAndParameterMethod);
		func2 += ReturnAndParameterMethod2;
		func2 += new Student().ReturnAndParameterMethodThree;
		func2 += Student.ReturnAndParameterMethodFour;
		func2 += ReturnAndParameterMethod;
		func2 += (x, y) =>
		{
			Console.WriteLine($"这是lambda表达式{x},{y}");
			return 1;
		};
		func2.Invoke(3, "4");

		foreach (Func<int, string, int> func in func2.GetInvocationList())
		{
			//func.BeginInvoke(3,"4",null,null);
			Task.Run(() => func.Invoke(3,"4"));
		}
	}
}

调用:

static void Main(string[] args)
{
	FrameWorkDelegate frameWorkDelegate = new FrameWorkDelegate();
	frameWorkDelegate.show();
}

执行结果: 在这里插入图片描述

2.增加方法(-=)

示例:

public void show()
{
	Console.WriteLine("============无参无返回值示例============");
	Action action1 = new Action(NoReturnNoParameterMethod);
	action1 += NoReturnNoParameterMethod2;
	//Student类中的一个无参无返回值的方法
	action1 += new Student().NoReturnNoParameterMethodThree;
	//Student类中的一个无参无返回值的静态方法
	action1 += Student.NoReturnNoParameterMethodFour;
	//可重复添加同一个方法
	action1 += NoReturnNoParameterMethod;
	action1 += () =>
	{
		Console.WriteLine("这是lambda表达式");
	};
	//移除方法
	action1 -= NoReturnNoParameterMethod;
	action1 -= NoReturnNoParameterMethod2;
	//此处移除无效,因为new Student()相当于每次都声明一个新的实例,所以此处与上面的并不是同一个实例导致移除无效。lambda表达式也无法移除,因为lambada表达式是在底层会生成一个方法。
	action1 -= new Student().NoReturnNoParameterMethodThree;
	action1 -= Student.NoReturnNoParameterMethodFour;
	action1.Invoke();
}

结果: 在这里插入图片描述


七、事件和委托

示例背景:猫叫了→老鼠跑了→狗叫了→小孩哭了(猫叫了一声引发了一系列反应)
解决方案:

1.在猫叫的方法中按照顺序调用方法

----依赖太重、职责不单一

public class Cat
{
	/// <summary>
	/// 猫叫了
	/// </summary>
	public void Meow()
	{
		Console.WriteLine("猫叫了");
		#region 普通的方法
		new Mouse().Run();
		new Dog().Bark();
		new Baby().Cry();
		#endregion
	}
}

public class Mouse
{
	/// <summary>
	/// 老鼠跑了
	/// </summary>
	public void Run()
	{
		Console.WriteLine("老鼠跑了");
	}
}

public class Dog
{
	/// <summary>
	/// 狗叫了
	/// </summary>
	public void Bark()
	{
		Console.WriteLine("狗叫了");
	}
}

public class Baby
{
	/// <summary>
	/// 小孩哭了
	/// </summary>
	public void Cry()
	{
		Console.WriteLine("小孩哭了");
	}
}

2.观察者模式(将不属于我的东西转移出去)

①.多播委托----从语法层面支持

此处有两个几乎一模一样的例子:
例子a是委托
例子b是事件
关系:事件是委托的实例(实例是特殊的委托)。
区别:事件比委托更安全
①.事件只能在所在类的内部执行(比如:子类无法使用父类中的事件,但是可以使用父类中的委托。实现类中无法调用目标类中的事件,但是可以调用目标类中的委托)
②.委托可以直接赋值null进行清空,事件不可以。
事件应用场景:当需求固定要在某一个动作之后再按照顺序执行一些方法的时候,使用事件防止别人跳过动作调执行方法。

a.多播委托

----将所有的方法通过多播委托+=到action中,使每个方法的职责单一,保证了所有方法的稳定性
使用到的类:

public class Cat
{
	/// <summary>
	/// 猫叫了
	/// </summary>
	public void Meow()
	{
		Console.WriteLine("猫叫了");
	}

	#region 多播委托用到的
	public Action catAction = null;

	public void CatActionMethod()
	{
		Console.WriteLine("开始蝴蝶效应了");
		if (catAction!=null)
		{
			catAction.Invoke();
		}
	}
	#endregion
}

public class Mouse
{
	/// <summary>
	/// 老鼠跑了
	/// </summary>
	public void Run()
	{
		Console.WriteLine("老鼠跑了");
	}
}

public class Dog
{
	/// <summary>
	/// 狗叫了
	/// </summary>
	public void Bark()
	{
		Console.WriteLine("狗叫了");
	}
}

public class Baby
{
	/// <summary>
	/// 小孩哭了
	/// </summary>
	public void Cry()
	{
		Console.WriteLine("小孩哭了");
	}
}

使用多播委托进行调用:

static void Main(string[] args)
{
	Cat cat = new Cat();
	#region 多播委托
	cat.catAction += cat.Meow;
	cat.catAction += new Mouse().Run;
	cat.catAction += new Dog().Bark;
	cat.catAction += new Baby().Cry;
	cat.CatActionMethod();
	#endregion
}

执行结果: 在这里插入图片描述

b.事件

事件观察者模式的三大要素:
1.发布者
2.订阅者
3.订阅。
多在winform中的控件事件绑定使用,ASP.NET MV5的管道处理模型就是通过十九大事件完成的。

基于a的实例增加一个Event关键字
用到的类:

public class Cat 
{
	/// <summary>
	/// 猫叫了
	/// </summary>
	public void Meow()
	{
		Console.WriteLine("猫叫了");
	}

	#region 事件用到的
	public event Action catEventAction = null;

	public void CatEventActionMethod()
	{
		if (catEventAction != null)
		{
			Console.WriteLine("开始蝴蝶效应了");
			catEventAction.Invoke();
		}
	}
	#endregion
}

public class Mouse 
{
	/// <summary>
	/// 老鼠跑了
	/// </summary>
	public void Run()
	{
		Console.WriteLine("老鼠跑了");
	}
}

public class Dog 
{
	/// <summary>
	/// 狗叫了
	/// </summary>
	public void Bark()
	{
		Console.WriteLine("狗叫了");
	}
}

public class Baby 
{
	/// <summary>
	/// 小孩哭了
	/// </summary>
	public void Cry()
	{
		Console.WriteLine("小孩哭了");
	}
}

调用:

static void Main(string[] args)
{
	Cat cat = new Cat();
	#region 事件
	cat.catEventAction += cat.Meow;
	cat.catEventAction += new Mouse().Run;
	cat.catEventAction += new Dog().Bark;
	cat.catEventAction += new Baby().Cry;
	cat.CatEventActionMethod();
	#endregion
}

执行结果: 在这里插入图片描述

②.面向对象实现----从程序设计层面支持

----定义一个接口,所有的类都继承这个接口并实现里面的共通方法,最后在执行方法中循环并调用
使用到的类:

/// <summary>
/// 定义一个通用接口(第二种观察者模式)
/// </summary>
public interface IObject
{
	void DoAction();
}	

public class Cat : IObject
{
	/// <summary>
	/// 猫叫了
	/// </summary>
	public void Meow()
	{
		Console.WriteLine("猫叫了");
	}

	#region 第二种观察者模式
	public List<IObject> observerlist = new List<IObject>();
	public void CatObserver()
	{
		Console.WriteLine("开始蝴蝶效应了");
		if (observerlist.Count > 0)
		{
			foreach (var item in observerlist)
			{
				item.DoAction();
			}
		}
	}

	public void DoAction()
	{
		this.Meow();
	}
	#endregion
}

public class Mouse : IObject
{
	public void DoAction()
	{
		this.Run();
	}

	/// <summary>
	/// 老鼠跑了
	/// </summary>
	public void Run()
	{
		Console.WriteLine("老鼠跑了");
	}
}

public class Dog : IObject
{
	/// <summary>
	/// 狗叫了
	/// </summary>
	public void Bark()
	{
		Console.WriteLine("狗叫了");
	}

	public void DoAction()
	{
		this.Bark();
	}
}

public class Baby : IObject
{
	/// <summary>
	/// 小孩哭了
	/// </summary>
	public void Cry()
	{
		Console.WriteLine("小孩哭了");
	}

	public void DoAction()
	{
		this.Cry();
	}
}

调用:

static void Main(string[] args)
{
	Cat cat = new Cat();

	#region 第二种观察者模式
	cat.observerlist.Add(new Cat());
	cat.observerlist.Add(new Mouse());
	cat.observerlist.Add(new Dog());
	cat.observerlist.Add(new Baby());
	cat.CatObserver();
	#endregion
}

执行结果: 在这里插入图片描述

八、标准的事件定义(简单的事件演示)

使用到的Demo类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MyDelegateProject
{
	/// <summary>
	/// 第一步创建一个类
	/// 关注我的公众号
	/// </summary>
	public class EventDelegateDemo
	{
		/// <summary>
		/// 发布者的实例
		/// </summary>
		private static PublicNumber publicNumber = new PublicNumber()
		{
			Id = 1,
			//公众号名字
			Name = "小张的琐碎日记"
		};

		/// <summary>
		/// 订阅:关联发布者和订阅者之间的关系(初始化他们的关系)
		/// </summary>
		public static void Subscribe()
		{
			#region 建立发布者和订阅者之间的关系
			//订阅者的实例
			Friends friends = new Friends()
			{
				Id = 1,
				Name = "美女"
			};
			//订阅者的实例
			Others others = new Others()
			{
				Id = 1,
				Name = "路人"
			};

			//建立发布者和订阅者之间的关系
			publicNumber.Push += friends.Read;
			publicNumber.Push += others.ReadAndAddFriends;
			#endregion
		}

		public static void Publish()
		{
			//公众号中发布一篇文章
			publicNumber.PublishArticles();
		}

	}

	/// <summary>
	/// 公众号类
	/// 发布者:对外发布事件,当触发一个动作后触发事件
	/// </summary>
	public class PublicNumber
	{
		/// <summary>
		/// 公众号的ID
		/// </summary>
		public int Id { get; set; }

		/// <summary>
		/// 公众号的名称
		/// </summary>
		public string Name { get; set; }

		/// <summary>
		/// 发布者的动作,发布文章
		/// </summary>
		public void PublishArticles()
		{
			MessageInfo messageInfo = new MessageInfo()
			{
				Id=1,
				Title= "关于事件观察者这件事",
				Description="这是这篇文章的内容",
				VXCode="ThisisVXCode"
			};

			Console.WriteLine($"发布一篇标题为《{messageInfo.Title}》的文章");

			Push.Invoke(this,messageInfo);
		}

		/// <summary>
		/// 
		/// </summary>
		public event EventHandler Push;
	}

	/// <summary>
	/// 关注公众号的人(朋友)
	/// 事件观察者模式的三大要素:订阅者:对发布的事情表示关注
	/// </summary>
	public class Friends
	{
		public int Id { get; set; }

		/// <summary>
		/// 朋友的名字
		/// </summary>
		public string Name { get; set; }

		/// <summary>
		/// 浏览公众号信息的动作
		/// </summary>
		public void Read(object? sender, EventArgs e)
		{
			MessageInfo mi=(MessageInfo)e ;
			Console.WriteLine($"朋友们观看了:Title:{mi.Title},Description:{mi.Description}");
		}
	}

	/// <summary>
	/// 关注公众号的人(其他人)
	/// 事件观察者模式的三大要素:订阅者:对发布的事情表示关注
	/// </summary>
	public class Others
	{
		public int Id { get; set; }

		/// <summary>
		/// 老师的名字
		/// </summary>
		public string Name { get; set; }

		/// <summary>
		/// 增加新人动作
		/// </summary>
		public void ReadAndAddFriends(object? sender, EventArgs e)
		{
			MessageInfo mi = (MessageInfo)e;
			Console.WriteLine($"陌生人观看了:Title:{mi.Title},Description:{mi.Description},并加作者微信:{mi.VXCode}");
		}
	}

	/// <summary>
	/// 公众号推送的内容
	/// </summary>
	public class MessageInfo : EventArgs
	{
		public int Id { get; set; }

		/// <summary>
		/// 标题
		/// </summary>
		public string Title { get; set; }

		/// <summary>
		/// 内容
		/// </summary>
		public string Description{get;set;}

		/// <summary>
		/// 作者微信号
		/// </summary>
		public string VXCode { get; set; }
	}
}

调用方法:

static void Main(string[] args)
{
	//第一步 初始化发布者和订阅者之间的关系
	EventDelegateDemo.Subscribe();
	//第二步 执行事件
	EventDelegateDemo.Publish();
}