`
文章目录
提示:以下是本篇文章正文内容,下面案例可供参考
一、什么是委托
委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。 在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联。 跟方法有点类似,有参数有返回值,拥有关键字delegate,是一种特殊的访问。委托的本质是个一个类,继承自特殊类MulticastDelegate(无法继承),MulticastDelegate继承自Delegate类。 委托可以通过new实例化,要求传递一个和这个委托的参数和返回值完全匹配的方法。
例如:
二、委托的作用和意义
代码:
namespace DelegateCourse
{
//动物类
public class Animal
{
public int Id { get; set; }
public string breed { get; set; } //品种
public void shout(AnimalTypeEnum type)
{
switch (type)
{
case AnimalTypeEnum.dog:
Console.WriteLine("汪");
break;
case AnimalTypeEnum.cat:
Console.WriteLine("喵");
break;
case AnimalTypeEnum.duck:
Console.WriteLine("嘎");
break;
default:
throw new Exception("NO AnimalTypeEnum");
}
}
public void dogShout()
{
Console.WriteLine("汪");
}
public void catShout()
{
Console.WriteLine("喵");
}
public void duckShout()
{
Console.WriteLine("嘎");
}
}
public enum AnimalTypeEnum
{
dog = 1,
cat = 2,
duck = 3
}
}
假设有这样一个简单场景,日常调用判断是哪只动物叫,我们可以使用if判断或者switch,但是对于后续扩展需要(增加一种动物的叫声的方法)就需要去改动原有代码,还重新进行测试。再假如我们需要增加公共业务逻辑,就是我在调用方法前或后需要增加其他动作,每个方法就都需要增加,就会出现大量的重复代码!!!
那么我们就可以思考,传递枚举的时候,目的是为了选择不同的业务逻辑,既然最终都是要选择业务逻辑的,那我们就可以直接传递业务逻辑;就需要把方法当做参数一样传递过来;委托在实例化的时候,委托就要一个方法;就可以传递一个委托,把方法放在委托中传递过来;
代码:
//定义一个委托
public delegate void ShoutDalegate();
在Animal类中增加一个执行业务的方法
public void ShoutDelegateMethod(ShoutDalegate shout)
{
Console.WriteLine("叫之前,先张嘴。。。");
shout.Invoke();
Console.WriteLine("叫之后,再闭嘴。。。");
}
//执行
Animal animal = new Animal();
ShoutDalegate del=new ShoutDalegate(animal.dogShout);
animal.ShoutDelegateMethod(del);
结果:
这样 我们达到我们想要的效果,相当于我只负责传递业务逻辑,每个方法逻辑都是独立的,也就达到了解耦的效果。
问:什么情况下?可以考虑使用委托?
1.方法内部业务逻辑耦合严重—考虑使用委托
2.如果多个方法,有很多重复代码—去掉重复代码–逻辑重用—考虑使用委托
三、委托的嵌套使用.netCore的核心设计–委托的多层嵌套
多层嵌套相当于我们的委托嵌套委托,业务逻辑嵌套,一层套一层,用简单的图和代码表示:
public class DelegateExtension{
public delegate void ShowDelegate();
public void method(){
ShowDelegate showMthod1 = new ShowDelegate(() =>
{
showMthod.Invoke();
});
ShowDelegate showMthod2 = new ShowDelegate(() =>
{
showMthod1.Invoke();
});
ShowDelegate showMthod3 = new ShowDelegate(() =>
{
showMthod2.Invoke();
});
ShowDelegate showMthod4 = new ShowDelegate(() =>
{
showMthod3.Invoke();
});
}
}
那么我们在实现的时候我们可以利用特性+反射去实现多层嵌套。
我们先定义几个特性:
public abstract class AbstractMethodAttribute : Attribute
{
/// <summary>
/// 在xxx 执行之前执行的点业务落
/// </summary>
public abstract ShowDelegate Do(ShowDelegate action);
}
public class BeforeMethodAttribute : AbstractMethodAttribute
{
/// <summary>
/// 在xxx 执行之前执行的点业务落
/// </summary>
public override ShowDelegate Do(ShowDelegate action)
{
ShowDelegate actionResult = new ShowDelegate(() =>
{
{
Console.WriteLine("任意逻辑1"); //这里可以随便写。。。。
}
action.Invoke();
{
Console.WriteLine("任意逻辑2"); //这里可以随便写。。。。
}
});
//actionResult.Invoke();
return actionResult;
}
}
public class BeforeWriteLogAttribute : AbstractMethodAttribute
{
/// <summary>
/// 在xxx 执行之前执行的点业务落
/// </summary>
public override ShowDelegate Do(ShowDelegate action)
{
ShowDelegate actionResult = new ShowDelegate(() =>
{
{
Console.WriteLine("任意逻辑3"); //这里可以随便写。。。。
}
action.Invoke();
{
Console.WriteLine("任意逻辑4"); //这里可以随便写。。。。
}
});
//actionResult.Invoke();
return actionResult;
}
}
public class BeforeWriteLogTEstAttribute : AbstractMethodAttribute
{
public override ShowDelegate Do(ShowDelegate action)
{
ShowDelegate actionResult = new ShowDelegate(() =>
{
{
Console.WriteLine("任意逻辑5"); //这里可以随便写。。。。
}
action.Invoke();
{
Console.WriteLine("任意逻辑6"); //这里可以随便写。。。。
}
});
//actionResult.Invoke();
return actionResult;
}
}
public class BeforeWriteLogTEst1Attribute : AbstractMethodAttribute
{
public override ShowDelegate Do(ShowDelegate action)
{
ShowDelegate actionResult = new ShowDelegate(() =>
{
{
Console.WriteLine("任意逻辑7"); //这里可以随便写。。。。
}
action.Invoke();
{
Console.WriteLine("任意逻辑8"); //这里可以随便写。。。。
}
});
//actionResult.Invoke();
return actionResult;
}
}
然后我们把特性定义在核心方法上:
/// <summary>
/// 普通类---Method方法;
/// </summary>
public class InvokerAction
{
[BeforeWriteLogTEstAttribute]
[BeforeWriteLogAttribute]
[BeforeMethodAttribute]
public void Method(int i)
{
Console.WriteLine(i);
Console.WriteLine("这里是要执行的核心业务逻辑");
}
}
核心代码:
public delegate void ShowDelegate();
public static void test()
{
InvokerAction invokerAction = new InvokerAction();
Type type = invokerAction.GetType();
MethodInfo methodInfo = type.GetMethod("Method");
///实例化一个委托:委托内部包含了一个行为: 行为:执行Method方法
ShowDelegate showMthod = new ShowDelegate(() =>
{
methodInfo.Invoke(invokerAction, null);
});
ShowDelegate showMethod = new ShowDelegate(() =>
{
methodInfo.Invoke(invokerAction, new object[] { 123 });
});
if (methodInfo.IsDefined(typeof(AbstractMethodAttribute), true))
{
foreach (AbstractMethodAttribute attribute in methodInfo.GetCustomAttributes().Reverse()) //获取特性示例
{
showMethod = attribute.Do(showMethod);
}
}
showMethod.Invoke();
}
这样我们就可以在每一次执行的时候,增加一些业务逻辑;这也是AOP的支持;涉及装饰器设计模式。它也可以更换特性顺序调整逻辑,具体需要自己运行调试体验!!
四、内置委托Action/Func
Action:有参数没有返回值,支持最大传参16
Func:有参数有返回值,最后一个参数类型是返回值类型,最大支持16个传参,第17个事返回值类型
既然系统提供委托更多是希望统一使用,就不需要自己再去定义委托!!!
五、多播委托/观察者模式
多播委托:任何一个委托都是继承自MulticasDelegate,定义的所有的委托都是多播委托
作用:可以通过+=把多个方法添加到这个委托中去,形成一个方法的执行链;执行委托的时候会按照添加方法的顺序依次执行。
问:这里能否使用action.beginInvoke();
答:不行,BeginInvoke是开启一个新的线程去执行委托,注册有多个方法的委托不能使用!!
问:如果要开启一个新的线程去执行委托呢?
答:可以通过action.GetInvocationList(); 得到一个delegate集合,获取到所有的委托,然后循环,每个方法执行的时候就可以可以新线程
有+=自然也有-=,去掉某个业务逻辑,比如我只吃饭不想洗碗。
-=移除方法,是从后往前,逐个匹配,匹配到就移除且停止匹配!!!
注意:如果是不同实例的方法是无法移除成功的,例如:action+=new xx().test; action-=new xx().test;
如果是lamda表达式也是无法移除,因为lamda表达式是在底层会生成一个方法。
六、事件
事件:只能在当前类的内部执行,事件是委托的实例,事件是特殊的委托。
事件与委托的区别:
1.委托关键字是delegate,事件的关键字增加event(Event Action)。
2.事件相对于委托更加安全(事件只能在类的内部执行)。
3.事件从外面操作,只能有一个+=/-=
这里我们能看到事件在外部执行Invoke时是不可以的!!!我们只能在定义事件的当前类中执行逻辑。两者区别不大。
1.事件解读
在C#中最常见的事件在桌面程序中比较常见,下面就以Winform举例:
我们在winform窗体中添加一个按钮,双击按钮会生成一个方法出来:
按钮其实是一个Button对象实例:继承Control—Control有一个Click事件—事件( EventHandler(object? sender, EventArgs e)) ,Form1构造函数函数中有一个InitializeComponent方法;在InitializeComponent方法中初始化Button按钮实例,个Button的实例中的Click事件+=一个动作(button1_Click方法)
Form1构造函数函数中有一个InitializeComponent方法;在InitializeComponent方法中初始化Button按钮实例,给Button的实例中的Click事件+=一个动作(button1_Click方法)
点击按钮,就会触发事件-,触发事件就是执行注册事件的方法
为什么要这么设计呢?为什么要使用事件
1.程序运行时,句柄会被监听,监听鼠标的点击的动作,出发操作系统,操作系统就要去找这个句柄是在哪个应用程序中,执行这个控件的事件出发方法。
2.就是为了把固定的逻辑或公共业务逻辑放在内部,把可变的逻辑放在外部,注册事件什么动作,就执行什么动作,可以让不同的场景共用相同的业务逻辑,分配指定各自不同的业务逻辑;对于可变的业务逻辑可以做到自由伸缩
2.自定义标准事件
我们针对标准实例做一个模拟场景:
StandardEvent.Init(); //初始化关联信息
StandardEvent.start();
public class StandardEvent
{
//学校公告发布者
private static SchoolSender Sender = new SchoolSender()
{
Id = 111,
Name = "xxx学校"
};
public static void start()
{
Sender.PublishShow();//发布了一则公告
}
public static void Init()
{
///订阅者的实例
Teacher tea = new Teacher()
{
Id = 222,
Name = "老师"
};
//订阅者的实例
Students pp = new Students()
{
Id = 333,
Name = "同学A"
};
//建立发布者和订阅者之间的关系
Sender.Push += tea.Read;
Sender.Push += pp.AddStu;
}
/// <summary>
/// 学校公告
/// 发布者:对完发布事件;当触发一个动作后,触发这个事件
/// </summary>
public class SchoolSender
{
public int Id { get; set; }
public string Name { get; set; }
public void PublishShow()
{
Console.WriteLine("发布了一片关于练习册的文章。。。");
Push.Invoke(this, new MessageInfo()
{
Id = 567,
Title = "语文",
Description = "语文练习册",
TeacherWechatNum = "xxx1"
});
}
public event EventHandler Push;
}
/// <summary>
/// 订阅者:对发布者发布的事情关注
/// 关注学校的公告
/// </summary>
public class Teacher
{
public int Id { get; set; }
public string Name { get; set; }
public void Read(object? sender, EventArgs e)
{
MessageInfo info = (MessageInfo)e;
Console.WriteLine("监督各位同学早点做完练习!!");
}
}
/// <summary>
/// 订阅者:对发布者发布的事情关注
/// 关注学校公告
/// </summary>
public class Students
{
public int Id { get; set; }
public string Name { get; set; }
public void AddStu(object? sender, EventArgs e)
{
MessageInfo info = (MessageInfo)e;
Console.WriteLine($"{Name}同学,你完成练习册了吗??");
}
}
public class MessageInfo : EventArgs
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string TeacherWechatNum { get; set; }
}
}
总结
文章有差异的地方请在评论区指出,望共勉!!!!!