有个关于事件的问题一直困扰着我,比方说代码这样写

this.dgv_User.CellValueChanged -= new System.Windows.Forms.DataGridViewCellEventHandler(this.dgv_User_CellValueChanged);
//------------
//这里面又调用了另一个方法,这个方法里面也有这么两句
this.dgv_User.CellValueChanged -= new System.Windows.Forms.DataGridViewCellEventHandler(this.dgv_User_CellValueChanged);


this.dgv_User.CellValueChanged += new System.Windows.Forms.DataGridViewCellEventHandler(this.dgv_User_CellValueChanged);
//------------
this.dgv_User.CellValueChanged += new System.Windows.Forms.DataGridViewCellEventHandler(this.dgv_User_CellValueChanged);

这样的结果就是最后dgv的CellValueChanged被重复绑定了this.dgv_User_CellValueChanged事件,当dgv的单元格值发生变化的时候就会触发两次,不管你绑了多少次这样程序的效率就降低了.有个错误我要纠正,以前我以为C#中事件的订阅不像VB那样,VB中直接RemoveHandler就能把所有绑定的方法移除,然后经过测试发现VB跟C#是一样的,也会重复绑定,RemoveHandler也只能一次一次的取消绑定怎么避免这个问题呢?
方法一:人为的控制dgv的CellValueChanged事件被订阅的次数,移除一个,绑定一个;这种方式只能做到尽可能降低事件被重复订阅的几率,因为当一个程序比较复杂的时候,难免会发生在一个函数(包含事件的订阅和取消订阅注销)里面嵌套另一个函数(包含事件的订阅和取消订阅注销),这样仍然会导致性能下降,久而久之变成顽疾。
方法二:让我们来一起研究我写了这样一个事件

public class TestEvent
{
public delegate void DataGridViewCellEventNewHandler(object sender, DataGridViewCellEventArgs e);
public event DataGridViewCellEventNewHandler CellValueChangedNew;

public int GetEventCount()
{
if (CellValueChangedNew == null)
return 0;
else
return CellValueChangedNew.GetInvocationList().Count();
}
}

注意,这里的DataGridViewCellEventNewHandler不是DataGridView的DataGridViewCellEventHandler;然后在程序中使用这个类的事件

TestEvent testEvent = new TestEvent();
testEvent.CellValueChangedNew
-= new TestEvent.DataGridViewCellEventNewHandler(this.dgv_User_CellValueChanged);
if (testEvent.GetEventCount() == 0)
testEvent.CellValueChangedNew
+= new TestEvent.DataGridViewCellEventNewHandler(this.dgv_User_CellValueChanged);

  事件本质上是一个MulticastDelegate对象,所以使用MulticastDelegate的GetInvocationList方法可以得到事件被订阅的列表,也就是调用这个事件的函数列表,然后用count返回所有的被订阅次数。想到这里,我们是不是可以重写DataGridview控件,加上一个监控CellValueChanged被订阅次数的方法,现在我们来试下。

public class CustomDataGridView : DataGridView
{
public int GetEventCount()
{
if (this.CellValueChanged == null)
return 0;
else
return CellValueChanged.GetInvocationList().Count();
}
}

编译一下,发现报错。原因是错误 事件“System.Windows.Forms.DataGridView.CellValueChanged”只能出现在 += 或 -= 的左边 。原来虽然CellValueChanged事件虽然是MulticastDelegate对象,但是如果要使用MulticastDelegate的GetInvocationList方法却只能在最初定义CellValueChanged事件的类的内部才能使用,就像我写的TestEvent里面,因为事件是内部定义的,所以能够使用GetInvocationList方法。既然不能用他写的事件,那么我们自己写一个CellValueChanged不行吗?试一下

public class CustomDataGridView : DataGridView
{
public delegate void DataGridViewCellEventNewHandler(object sender, DataGridViewCellEventArgs e);
public event DataGridViewCellEventNewHandler CellValueChangedNew;
public int GetEventCount()
{
if (CellValueChangedNew == null)
return 0;
else
return CellValueChangedNew.GetInvocationList().Count();
}
}

重写了DataGridview,自己定义了CellValueChanged事件,控件写好了,看看能不能用
this.dgv_User.CellValueChangedNew+=new CustomDataGridView.DataGridViewCellEventNewHandler(dgv_User_CellValueChangedNew);
程序跑起来,却发现无论怎么改变单元格的值都不能触发dgv_User_CellValueChangedNew事件,看来这么做还是不行啊,至于为什么没有触发这个事件我也很纳闷,有经验的朋友可以教教我。后来在csdn上发帖,也没有得到很大的帮助,就在这个时候,向技术总监王总发出求助,他给了一个贴子,刚好可以解决我的问题,用的是反射。这是那个帖子的源码

  

EventHandlerList buttonEvents = (EventHandlerList)this.button1.GetType().InvokeMember("Events", System.Reflection.BindingFlags.GetProperty | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, this.button1, null);
buttonEvents.GetType().GetMethod(
"button1_Click");
PropertyInfo propertyInfo
= (typeof(System.Windows.Forms.Button)).GetProperty("Events", BindingFlags.Instance | BindingFlags.NonPublic);
EventHandlerList eventHandlerList
= (EventHandlerList)propertyInfo.GetValue(button1, null);
FieldInfo fieldInfo
= (typeof(Control)).GetField("EventClick", BindingFlags.Static | BindingFlags.NonPublic);
Delegate d
= eventHandlerList[fieldInfo.GetValue(null)];
if (d != null)
{
foreach (Delegate temp in d.GetInvocationList())
{
Console.WriteLine(temp.Method.Name);
//这个地方可以清除所有的委托,也可以使用条件清除指定委托,没有办法直接清除所有的
}
}

  C#的Control封装了EventHandlerList, 但它是protected的, 所以我们不能简单的看到它的存在, 不过, 如果你走Debug Mode的话, 还是可以看得很清楚的, 但如果真要把它拿出来用, 就只能用Reflect了
上面的代码是button的,怎么用到DataGridview上来,只要稍加改动

 private void ClearEvent()
{
PropertyInfo propertyInfo
= (typeof(System.Windows.Forms.CustomDataGridView)).GetProperty("Events", BindingFlags.Instance |

BindingFlags.NonPublic);
EventHandlerList eventHandlerList
= (EventHandlerList)propertyInfo.GetValue(dgv_User, null);
FieldInfo fieldInfo
= (typeof(DataGridView)).GetField("EVENT_" + "DATAGRIDVIEW" + "CELLVALUECHANGED", BindingFlags.Static |

BindingFlags.NonPublic);
Delegate d
= eventHandlerList[fieldInfo.GetValue(this.dgv_User)];
if (d != null)
{
foreach (Delegate temp in d.GetInvocationList())
{
//这个地方可以清除所有的委托,也可以使用条件清除指定委托,没有办法直接清除所有的
}
}
}

  这里要注意的是GetField方法里面的string name参数,如果是DataGridView的事件都要是这种格式"EVENT_" + "DATAGRIDVIEW" + "CELLVALUECHANGED"(event+dgv+大写的事件名),不然返回的都是null。

  大功告成,现在可以用temp的MulticastDelegate对象的方法得到你想要的东西,单个取消订阅、取消所有订阅、得到所有被订阅的次数、订阅该事件的所有函数等等等等信息。困扰N久的问题终于解决了
第一次在这大园子里发文章,可能很多地方说的不专业,请大侠们指教。

  

作者: 火麒麟 发表于 2011-08-11 11:47 原文链接

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"