最近在琢磨一些事情,和API设计有关。API设计在很多时候是和语言特性有关的,因此如Java这样的语言,在API设计时会处处受到压抑。而C#就能够出现如Moq或Fluent NHIbernate这样的项目。同样,F#能够开发出FsTest,Scala号称Scalable Language,都是依靠着丰富的语言特性。不过,最近在使用C#的时候鼻子上也碰了一点灰,这是因为我发现“事件”这个东西没法作为对象进行传递。

public class Program
{public event EventHandler Submit;
}

  我们如果要为这个事件添加处理函数自然只要:

var myClass = new MyClass();
myClass.Submit += (sender, eventArgs) => Console.WriteLine(sender);

  但是,如果我想写一个“统一添加”的辅助函数,例如可以这样调用:

RegisterHandlers(myClass.Submit);

  就会发现——做不到。虽然,如果我们提供这样的RegisterHandlers方法的实现:

class Program
{public event EventHandler Submit;static void RegisterHandlers(EventHandler ev){ev += (sender, eventArgs) => Console.WriteLine("sender");}static void Main(string[] args){Program p = new Program();RegisterHandlers(p.Submit);p.Submit("Hello World", EventArgs.Empty);}
}

  这是可以编译通过的,似乎……应该也过得去。但是实际执行的时候就会发现,p.Submit事件在触发的时候依然会抛出NullReferenceException异常(为什么?)。因此,我们必须选择另外一种方式。

  我们知道,虽说是一个事件,但是在注册和移除处理函数的时候,实际上都是在调用add方法和remove方法。例如这句代码:

myClass.Submit += (sender, eventArgs) => Console.WriteLine(sender);

  和下面的代码其实是“等价”的:

myClass.add_Submit((sender, eventArgs) => Console.WriteLine(sender));

  “等价”打上引号是因为add_Submit这行代码其实无法编译通过,我只是用来表示一个含义。但是这意味着,我们可以通过反射来调用add方法和remove方法。因此,我编写了这样的一个类:

public class Event
{public Event(Expression<Func> eventExpr){...}private object m_instance;private MethodInfo m_addMethod;private MethodInfo m_removeMethod;public Event AddHandler(T handler){this.m_addMethod.Invoke(this.m_instance, new object[] { handler });return this;}public Event RemoveHandler(T handler){this.m_removeMethod.Invoke(this.m_instance, new object[] { handler });return this;}
}

  于是,我可以设法把一个事件封装为一个对象:

class Program
{public event EventHandler Submit;static void Main(string[] args){Program p = new Program();var ev = new Event<EventHandler>(() => p.Submit);ev.AddHandler((sender, eventArgs) => Console.WriteLine(sender));p.Submit("Hello World", EventArgs.Empty);}
}

  那么Event类的构造函数该怎么写呢?不过是解析表达式树而已:

public Event(Expression<Func> eventExpr)
{var memberExpr = eventExpr.Body as MemberExpression;this.m_instance = memberExpr.Expression == null ? null :Expression.Lambda<Func<object>>(memberExpr.Expression).Compile()();var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod |(this.m_instance == null ? BindingFlags.Static : BindingFlags.Instance);var member = memberExpr.Member;this.m_addMethod = member.DeclaringType.GetMethod("add_" + member.Name, bindingFlags);this.m_removeMethod = member.DeclaringType.GetMethod("remove_" + member.Name, bindingFlags);
}

  对于() => p.Submit这样的代码来说,它是一个MemberExpression,我们可以通过MemberExpression的属性来说的p的实例。然后,根据Submit属性的Member的Name便可以得出它的add或remove方法。其中需要再判断这是一个实例事件还是一个静态事件就可以了。总体来说,代码比较简单。当然,在实际运用中会要求在不合法的情况下抛出合适的异常。此外,如果您对性能有要求,也可以使用FastLambda即FastReflectionLib来提高性能。

  为了方便使用,我还为Event类重载了+和-两个操作符,以及一个EventFactory类:

public static class EventFactory
{public static Event Create(Expression<Func> eventExpr){return new Event(eventExpr);}
}
public class Event
{...public static Event operator +(Event ev, T handler){return ev.AddHandler(handler);}public static Event operator -(Event ev, T handler){return ev.RemoveHandler(handler);}
}

  EventFactory类的Create方法可以避免显式地提供T类型,而+和-操作符的目的便是在添加和删除事件处理函数的时候“更像那么一回事”。于是现在我们便可以写这样的代码:

class Program
{public event EventHandler Submit;static void Main(string[] args){Program p = new Program();var ev = EventFactory.Create(() => p.Submit);ev += (sender, eventArgs) => Console.WriteLine(sender);p.Submit("Hello World", EventArgs.Empty);Console.WriteLine("Press any key to exit...");Console.ReadLine();}
}

  既然有了Event对象,我们便可以把它作为参数传递给其他方法,然后在其他的方法中添加或删除事件处理函数。

  是不是挺美妙的?您也来下载完整代码试试看吧,而且说不定……您还能发现这个方法里的一个陷阱。我承认,其实这个解决方案会遇见C#的一个问题,它糊弄了我,也糊弄了大家……

艾伟_转载:把事件当作对象进行传递相关推荐

  1. 艾伟_转载:.NET设计模式:观察者模式(Observer Pattern)

    概述 在软件构建过程中,我们需要为某些对象建立一种"通知依赖关系" --一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知.如果这样的依赖关系过于紧密,将 ...

  2. 艾伟_转载:C#中的委托和事件-抛砖引玉

    最近在学习委托和事件,在书店里面看了好多书,但是都是迷迷的-- 今天在博客园里面看到了 张子阳 所写的博客C#中的委托和事件:http://www.tracefact.net/CSharp-Progr ...

  3. 艾伟_转载:.NET内存管理、垃圾回收

    1. Stack和Heap 每个线程对应一个stack,线程创建的时候CLR为其创建这个stack,stack主要作用是记录函数的执行情况.值类型变量(函数的参数.局部变量 等非成员变量)都分配在st ...

  4. 艾伟_转载:使用LINQ to SQL更新数据库(中):几种解决方案

    在前一篇文章中,我提出了在使用LINQ to SQL进行更新操作时可能会遇到的几种问题.其实这并不是我一个人遇到的问题,当我在互联网上寻找答案时,我发现很多人都对这个话题发表过类似文章.但另我无法满足 ...

  5. 艾伟_转载:C# WinForm开发系列 - TextBox

    包含金额/日期输入框,带弹出数字面板的计算输入框,安全密码输入等控件(文章及相关代码搜集自网络,仅供参考学习,版权属于原作者! ). 1.CalculatorBox    CalculatorBox. ...

  6. 艾伟_转载:探索.Net中的委托

    废话 我本来以为委托很简单,本来只想简简单单的说说委托背后的东西,委托的使用方法.原本只想解释一下那句:委托是面向对象的.类型安全的函数指针.可没想到最后惹出一堆的事情来,越惹越多,罪过,罪过.本文后 ...

  7. 艾伟_转载:C#语言基础常见问题汇总

    概述 1.什么是C#? C#是Microsoft公司设计的一种编程语言.它松散地基于C/C++,并且有很多方面和Java类似. Microsoft是这样描述C#的:"C#是从C和C++派生来 ...

  8. 艾伟_转载:.NET 4.0中数组的新增功能

    1.两数组是否"相等"? 在实际开发中,有时我们需要比对两个数组是否拥有一致的元素,例如,以下两个数组由于拥有相同的元素,因此被认为是相等的: int[] arr1 = new i ...

  9. 艾伟_转载:从ASP.NET的PHP执行速度比较谈起

    上星期我在InfoQ发表了一篇新闻,对Joe Stagner在博客上发表的三篇关于ASP.NET与PHP性能对比的文章进行了总结.写新闻其实挺不爽的,因为不能夹杂个人的看法,只能平铺直叙陈述事实.当然 ...

最新文章

  1. 微软发布预览版SQL Server跨平台开发工具
  2. 匿名内部类使用的场景之一
  3. CF788B Weird journey
  4. 有关机械手臂控制中的两个重要输入参数
  5. python实现两张图片横向和纵向拼接
  6. aspectj 注解
  7. 出现红字是电脑问题吗_婚姻出现问题,生个孩子就能解决,这是真的吗?
  8. 博文视点读书节第九日丨大咖书单加倍放送!
  9. 二叉树的存储方式以及递归和非递归的三种遍历方式
  10. 说你呢,装着JDK8,却孜孜不倦的写着 JDK6 的代码,写了3年了,JDK8的特性都没用过......
  11. alpha冲刺-事后诸葛亮
  12. cs七龙珠机器人_CS七龙珠机器人的命令
  13. Park 变换 系数2/3的由来
  14. Quora cqa问题抓取
  15. 游戏画质提升1《X战警金刚狼前传》画质增强修改
  16. Gradle Task的使用
  17. HGAME 2022 week1 个人部分WP
  18. 北大青鸟IT教育14%股权挂牌转让
  19. Git mvn 命令
  20. 互联网下半场,实体店的转型思考

热门文章

  1. android里的editText怎么用,Android自定义控件EditText使用详解
  2. java web 嵌套播放器_网页嵌套播放器
  3. mysql设置约束l命令_mysql建表约束,sql
  4. idea缩写快捷键_idea快捷键大全
  5. 一种互补间歇振荡器工作电压
  6. 信号与系统期末考试2020春季学期试题准备
  7. 2020年春季学期信号与系统课程作业参考答案-第十四次作业
  8. STC单片机的命名规则
  9. java 取不同的随机数_Java实现获取指定个数的不同随机数
  10. linux输出重定向%3e退出,Linux学习笔记——第二章:Linux的用户接口与文本编辑器...