彩票走势图

C# 如何移除所有和事件关联的委派函数

转帖|其它|编辑:郝浩|2011-08-11 14:57:00.000|阅读 2948 次

概述:本文主要介绍C# 如何移除所有和事件关联的委派函数,希望对大家有帮助。

# 慧都年终大促·界面/图表报表/文档/IDE等千款热门软控件火热促销中 >>

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

  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对象的方法得到你想要的东西,单个取消订阅、取消所有订阅、得到所有被订阅的次数、订阅该事件的所有函数等等等等信息。


标签:

本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@pclwef.cn

文章转载自:博客园

为你推荐

  • 推荐视频
  • 推荐活动
  • 推荐产品
  • 推荐文章
  • 慧都慧问
扫码咨询


添加微信 立即咨询

电话咨询

客服热线
023-68661681

TOP