我一直都认为,字典这种数据结构是个很好东西,简单而且强大,利用好了真是事半功倍。说到事件的时候,我们一定也要想到委托,在 net 的事件里,事件就是多播委托,这篇文章的角色主要就是字典和委托了。
在一个小项目中,对于数据的精准性可能要求不太严格,但是也会存在这样的逻辑,比如下单之后,记录日志,增加用户订单量,等等其他一些不必再事务中处理的操作(事务中处理的步骤越少越好),这些逻辑操作就算失败一两个也没什么关系,这个时候就没必要使用外部队列来确保执行了,这种情况下使用这篇文章说的应用程序内的事件发布和订阅就比较合适。
我们先定义事件管理器:
public class AppEventService { private readonly ConcurrentDictionary<string, Func<object[], object>> _eventHandlerDict = new ConcurrentDictionary<string, Func<object[], object>>(); }
其中的字典便是存储委托的容器,再提供一个添加订阅和发布的方法如下:
/// <summary> /// 添加事件处理程序 /// </summary> /// <param name="eventKey"></param> /// <param name="func"></param> public void AddHandler(string eventKey, Func<object[], object> func) { _eventHandlerDict.AddOrUpdate(eventKey, func, (key, oldFun) => { return oldFun += func; }); } /// <summary> /// 触发事件 /// </summary> /// <param name="eventKey"></param> /// <param name="ps"></param> /// <returns></returns> public Task Fire(string eventKey, params object[] ps) { return _eventHandlerDict.TryGetValue(eventKey, out var func) ? Task.Run(() => func(ps)) : Task.CompletedTask; }
基本功能是实现了,但是由于事件处理的委托需要一个一个添加,也是挺麻烦的,所以我们定义一个特性类,来标识应用程序内的事件处理程序:
/// <summary> /// 标记方法是appEvent的事件处理程序 /// </summary> [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public class AppEventHandlerAttribute : Attribute { public AppEventHandlerAttribute(string eventKey) { EventKey = eventKey; } /// <summary> /// key /// </summary> public string EventKey { get; set; } }
有了标识的特性类,我们就可以在 AppEventService 添加一个批量扫描的方法:
/// <summary> /// 扫码程序集,注册事件处理程序 /// </summary> /// <param name="assembly"></param> public void ScanEventHandler(Assembly assembly) { foreach (var type in assembly.GetTypes()) { var methodInfos = type.GetMethods(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic ^ BindingFlags.GetProperty ^ BindingFlags.SetProperty); foreach (var methodInfo in methodInfos) { var mehAttr = methodInfo.GetCustomAttribute<AppEventHandlerAttribute>(); if (mehAttr == null) continue; var fun = DynamicMethodHelper.GetExecuteDelegate(methodInfo); AddHandler(mehAttr.EventKey, (args) => fun(methodInfo.IsStatic ? null : Activator.CreateInstance(type), args)); } } }
在 asp.netcore 中,框架自带了 DI,我们再添加的代码来实现从容器中加载:
private IServiceProvider _serviceProvider; /// <summary> /// 实例化AppEventService /// </summary> public AppEventService() { } public void SetServiceProvider(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } /// <summary> /// 扫描容器中的服务,注册时间处理程序 /// </summary> /// <param name="services"></param> public void ScanEventHandler(IServiceCollection services) { foreach (var service in services) { var methodInfos = service.ServiceType.GetMethods( BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic ^ BindingFlags.GetProperty ^ BindingFlags.SetProperty); foreach (var methodInfo in methodInfos) { var mehAttr = methodInfo.GetCustomAttribute<AppEventHandlerAttribute>(); if (mehAttr == null) continue; var fun = DynamicMethodHelper.GetExecuteDelegate(methodInfo); AddHandler(mehAttr.EventKey, (args) => fun(methodInfo.IsStatic ? null : _serviceProvider.GetService(service.ServiceType), args)); } } }
然后安装常规约定,我们提供一个扩展方法来添加 AppEventService:
public static class AppEventExtensions { /// <summary> /// 注册应用程序域中所有有AppService特性的类 /// </summary> /// <param name="services"></param> public static void AddAppEvents(this IServiceCollection services) { AppEventService appEventService = new AppEventService(); appEventService.ScanEventHandler(services); services.AddSingleton(appEventService); } }
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于