您当前位置: 圣才学习网首页 >> IT类 >> .NET程序设计

将事件视为对象的一个编程实例

扫码手机阅读
用圣才电子书APP或微信扫一扫,在手机上阅读本文,也可分享给你的朋友。
评论(0
   
来源:网络 作者:未知
 
  这次我们是要编写一个
 
  对象,提供它AddHandlerRemoveHandler的实现。事实上,在之前还有一篇文章中,我们搞了一个人模狗样的构造方式,但是它往往不适合用于实际使用过程中。因此,其实
 
  最关键的地方还是各种不同的构造方式,使它可以用于各种情况。
 
  方法一:直接提供添加删除的实现
 
  在之前的文章里,已经有一些朋友提出了最简单的做法,即直接提供AddHandlerRemoveHandler的实现,例如:
 
  public class DelegateEvent
 
  hrow new ArgumentNullException("add");
 
  if (remove == null) throw new ArgumentNullException("remove");
 
  this.m_addHandler = add;
 
  this.m_removeHandler = remove;
 
  }
 
  private void CheckDelegateType()
 
  {
 
  if (!typeof(Delegate).IsAssignableFrom(typeof(TDelegate)))
 
  {
 
  throw new ArgumentException("TDelegate must be an Delegate type.");
 
  }
 
  }
 
  public DelegateEvent
 
  }
 
  }
 
  用户可以直接提供两个委托,分别在AddHandlerRemoveHandler方法中使用。在构造函数中我们除了进行参数的非null判断之外,还会进行委托类型的检查。C#在泛型约束方面的遗憾很多,例如我们无法将一个泛型参数约束为一个Delegate类型的子类。因此,我们使用 CheckDelegateType来检查类型是否兼容。
 
  于是我们便可以这样编写代码:
 
  var myClass = new MyClass();
 
  var de = new DelegateEvent
 
  += h,
 
  myClass.MyEvent -= h);
 
  这个方法的缺点也是比较明显的。那就是,我们无法限制addremove两个委托的实现是否正确,即时程序员提供了一些错误实现也只得老老实实地执行。例如,我们可能一不小心会在add操作里也用上了+=,或者一不小心在remove操作中去删除了另一个事件。而且,这样的问题还比较难以发现。此外,这种做法需要些的代码也比较多。一个优秀的API设计,应该让开发人员易于使用,也尽可能避免出现不必要的麻烦——至少,尽早把错误汇报出来。
 
  方法二:实例+事件名
 
  提供一个对象实例,再给出一个事件名,也可以构造一个DelegateEvent对象:
 
  public DelegateEvent(object obj, string eventName)
 
  {
 
  this.CheckDelegateType();
 
  if (obj == null) throw new ArgumentNullException("obj");
 
  if (String.IsNullOrEmpty(eventName)) throw new ArgumentNullException("eventName");
 
  this.BindEvent(obj.GetType(), obj, eventName);
 
  }
 
  private void BindEvent(Type type, object obj, string eventName)
 
  {
 
  var eventInfo = type.GetEvent(eventName,
 
  BindingFlags.Public | BindingFlags.NonPublic |
 
  (obj == null ? BindingFlags.Static : BindingFlags.Instance));
 
  if (eventInfo == null)
 
  {
 
  throw new ArgumentException
 
  String.Format("Event {0} is missing in {1}",
 
  eventName, type.FullName));
 
  }
 
  if (eventInfo.EventHandlerType != typeof(TDelegate))
 
  {
 
  throw new ArgumentException
 
  String.Format("Type of event {0} in {1} is mismatched with {2}.",
 
  eventName, type.FullName, typeof(TDelegate).FullName));
 
  }
 
  eventInfo.AddEventHandler(obj, (Delegate)(object)h);
 
  eventInfo.RemoveEventHandler(obj, (Delegate)(object)h);
 
  }
 
  得到了对象和事件名之后,自然要做的还是检查参数是否为空,以及TDelegate类型是否为一个委托,接着便是用BindEvent方法来绑定m_addHandlerm_removeHandler两个委托。在BindEvent方法中,主要使用了反射获取事件的eventInfo对象。对eventInfo的检查主要体现在两方面,一是是否存在这个事件,二是这个事件的委托类型是否等于TDelegatem_addHandler m_removeHandler委托的构造很简单,不过由于编译器不允许将一个不定类型TDelegate转化为Delegate,因此我们必须先将其转为object,再转化为Delegate并添加到eventInfo中。
 
  这个构造函数的使用方法如下:
 
  var myClass = new MyClass();
 
  var de = new DelegateEvent
 
  这个做法的缺点在于事件使用字符串来表示,这意味着错误只有在运行时才能改变。此外,如果您想通过重构来修改MyEvent事件的名称,编辑器也是无法为您修改这里的字符串的。因此,我们一直强调强类型,一个重要的目的便是获得静态检查及重构支持。
 
  此外,这个做法还无法帮定静态事件。因此,我们还要努力。
 
  方法三:类型+事件名
 
  既然是静态事件,那么绑定的就不是对象的事件,而是类型的事件。因此,第三种做法是提供一个表示类型的对象,以及一个事件名:
 
  public DelegateEvent(Type type, string eventName)
 
  {
 
  this.CheckDelegateType();
 
  if (type == null) throw new ArgumentNullException("type");
 
  if (String.IsNullOrEmpty(eventName)) throw new ArgumentNullException("eventName");
 
  this.BindEvent(type, null, eventName);
 
  }
 
  没错,就是这么简单。许多逻辑已经包含在BindEvent方法中。它的使用方法如下:
 
  var de = new DelegateEvent
 
  这里的缺点与之前相同,无法获得静态检查和重构支持。这似乎是没有办法的,即使在Reactive Framework中,微软朋友们也是使用字符串来绑定一个事件。
 
  方法四:使用表达式树指定事件
 
  这就是文章一开始提到的人模狗样的做法。他虽然有很大限制,但并不是一无是处。因此,也把它算作是一个方法吧:
 
  public DelegateEvent(Expression
 
  ;
 
  if (memberExpr == null)
 
  {
 
  throw new ArgumentNullException("eventExpr", "Not an event.");
 
  }
 
  object instance = null;
 
  // obj
 
  if (memberExpr.Expression != null)
 
  {
 
  try
 
  {
 
  obj
 
  var instanceExpr = Expression.Lambda
 
  this.BindEvent(memberExpr.Member.DeclaringType, instance, memberExpr.Member.Name);
 
  }
 
  经过了几次表达式树的组装和解析,不知道您是否还认为这是一个难以接触的话题呢?从注释中可以发现,其实它的每一步操作都是非常清晰的,可以方便而有条理地提取表达式中的信息。为了配合C#编译器的类型推断功能,我们还可以补充一个辅助方法来构造DelegateEvent的对象:
 
  public static class EventFactory
 
  {
 
  (Expression
 
  于是,我们便可以这样使用:
 
  class Program
 
  {
 
  public event EventHandler MyEvent;
 
  public static event EventHandler MyStaticEvent;
 
  static void Main(string[] args)
 
  {
 
  MyStaticEvent);
 
  var p = new Program();
 
  p.MyEvent);
 
  }
 
  }
 
  这个做法的优势是易于编程,可以享受静态检查和重构等福利。但是,它的限制也已经讨论过了,那就是只能用于定义事件的类中。也就是说,如果上面的代码离开了ProgramMain方法就无法编译通过了。因此,这个方法只适用于由定义事件的类亲自暴露出DelegateEvent对象的场景。如果您处于这个场景之下,那么它几乎就是您最好的选择了。
 
  总结
 
  这就是我的参考答案,不难,但似乎也不是可以一蹴而就的。这也是我搞趣味编程的目的,我希望可以把每个题目的解决方案集中在一小个范围内,将其各个方便挖掘出来,即使它们每个都很简单。例如,您可能很容易就能想到了第一种方法,但是您是否也准备了适合其它使用场景下的构造方式?如果您编写了各种构造方式,那么是否把异常情况都判断了呢?如果您判断了异常情况,是否提供了辅助的API来简化开发呢(如上面的EventFactory)?
 
  这些不是茴字有多少种写法的问题,这是在考察一个人是否考虑周全。据我观察,茴字有几种写法很多情况下都已经成为一些朋友为自己思考不周,或了解不深的进行开脱的理由了。只可惜,例如某个方法有几个异常方面,列举跨页面传递数据有几种方法,C#中的成员有哪些修饰符等等,都是某些著名大公司考察应聘者的题目。
 
  算了明说吧,就是微软,而且就是我亲自遇到的面试题。
 

小编工资已与此挂钩!一一分钱!求打赏↓ ↓ ↓

如果你喜欢本文章,请赐赏:

已赐赏的人
最新评论(共0条)评论一句