.Net 事件类型的实现和推荐做法

本文详细介绍了自定义事件的实现方式,并针对线程安全和性能问题提出了优化方案。通过对事件成员的重新定义和使用EventHandlerList,实现了更加高效且线程安全的事件处理。

首先来看一下我们常见的自定义事件实现方式,首先创建可能的事件参数

 1 /// <summary>
 2     /// 事件参数
 3     /// </summary>
 4     public sealed class DemoEventArgs:EventArgs {
 5 
 6         /// <summary>
 7         /// 获取或设置事件的上下文
 8         /// </summary>
 9         public string Context {
10             get { return context; }
11             set { context = value; }
12         }
13         private string context;
14 
15 
16     }
当然,如果在实际使用中不需要事件参数的话,可以直接使用EventArgs.Empty静态字段。
紧接着我们需要定义事件成员
1ExpandedBlockStart.gifContractedBlock.gif/**//// <summary>
2InBlock.gif        /// 事件成员
3ExpandedBlockEnd.gif        /// </summary>

4None.gif        public event EventHandler<DemoEventArgs> NewEv
然后,我们就需要引发这个事件
 1 public void InvokeNewEvent() {
 2             DemoEventArgs e = new DemoEventArgs();
 3             e.Context = "Hello world";
 4             this.OnNewEvent(e);
 5         }
 6 
 7         protected virtual void OnNewEvent(DemoEventArgs e) {
 8             if (NewEvent!=null) {
 9                 NewEvent(this, e);
10             }
11         }

对于上面的代码,我们通过Reflector工具可以看到,对于事件定义
public event EventHandler<DemoEventArgs> NewEvent;

编译程序会将其翻译成两个public方法。
event1.gif
相应代码如下
 1None.gif[MethodImpl(MethodImplOptions.Synchronized)]
 2None.gifpublic void add_NewEvent(EventHandler<DemoEventArgs> value)
 3ExpandedBlockStart.gifContractedBlock.gifdot.gif{
 4InBlock.gif    this.NewEvent = (EventHandler<DemoEventArgs>) Delegate.Combine(this.NewEvent, value);
 5ExpandedBlockEnd.gif}

 6None.gif
 7None.gif[MethodImpl(MethodImplOptions.Synchronized)]
 8None.gifpublic void remove_NewEvent(EventHandler<DemoEventArgs> value)
 9ExpandedBlockStart.gifContractedBlock.gifdot.gif{
10InBlock.gif    this.NewEvent = (EventHandler<DemoEventArgs>) Delegate.Remove(this.NewEvent, value);
11ExpandedBlockEnd.gif}

12None.gif
13None.gif 
14None.gif
15None.gif 
16None.gif

MethodImpOption枚举类型定义了方法是如何被执行的。Synchronized指定了同时只能由一个线程执行该方法。静态方法锁定类型,而实例方法锁定实例。这样做的目的是,保证在操作实例事件时,对于每一个对象,在同一时刻add和remove方法的线程安全。
在类型上,所有事件的add和remove方法都将使用相同的锁。这样造成,在多个线程同时对不同事件进行订阅和撤销的时候,就会出现性能损失。在MSDN上,我们可以查阅到最后由一个注意事项:“实例或类型上的锁定(如同使用 标志一样)对于公共类型是不推荐使用的,其原因在于除了不是自己的代码的其他代码可对公共类型和实例采用锁定。这可能导致死锁或其他同步问题。 ”
线程同步的指导方针是不应该在对象本身上加同步锁,因为同步锁将对所有的代码公开,这意味着任何人都有可能蓄意的编写代码Lock这个对象,造成其它线程死锁。

为此,在大多数情况下上述情况并不可能发生,但是对于一个完美而稳固的组件来说,这就显得相当重要。鉴于以上原因,我们重新定义事件的实现。

 1ExpandedBlockStart.gifContractedBlock.gif public class EventDemo dot.gif{
 2ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 3InBlock.gif        /// 私有同步锁
 4ExpandedSubBlockEnd.gif        /// </summary>

 5InBlock.gif        private readonly object _eventLock = new object(); 
 6InBlock.gif
 7ExpandedSubBlockStart.gifContractedSubBlock.gif        /**//// <summary>
 8InBlock.gif        /// 事件成员
 9ExpandedSubBlockEnd.gif        /// </summary>

10InBlock.gif        private event EventHandler<DemoEventArgs> internalNewEvent;
11ExpandedSubBlockStart.gifContractedSubBlock.gif        public event EventHandler<DemoEventArgs> NewEvent dot.gif{
12ExpandedSubBlockStart.gifContractedSubBlock.gif            add dot.gif{
13ExpandedSubBlockStart.gifContractedSubBlock.gif                lock (_eventLock) dot.gif{
14InBlock.gif                    internalNewEvent += value;
15ExpandedSubBlockEnd.gif                }

16ExpandedSubBlockEnd.gif            }

17ExpandedSubBlockStart.gifContractedSubBlock.gif            remove dot.gif{
18ExpandedSubBlockStart.gifContractedSubBlock.gif                lock (_eventLock) dot.gif{
19InBlock.gif                    internalNewEvent -= value;
20ExpandedSubBlockEnd.gif                }

21ExpandedSubBlockEnd.gif            }

22ExpandedSubBlockEnd.gif        }

23InBlock.gif
24ExpandedSubBlockStart.gifContractedSubBlock.gif        protected virtual void OnNewEvent(DemoEventArgs e) dot.gif{
25InBlock.gif            //出于线程考虑,委托字段保存到临时字段中
26InBlock.gif            EventHandler<DemoEventArgs> t = internalNewEvent;
27ExpandedSubBlockStart.gifContractedSubBlock.gif            if (t!=nulldot.gif{
28InBlock.gif                internalNewEvent(this, e);
29ExpandedSubBlockEnd.gif            }

30ExpandedSubBlockEnd.gif        }

31InBlock.gif
32ExpandedSubBlockStart.gifContractedSubBlock.gif        public void InvokeNewEvent() dot.gif{
33InBlock.gif            DemoEventArgs e = new DemoEventArgs();
34InBlock.gif            e.Context = "Hello world";
35InBlock.gif            this.OnNewEvent(e);
36ExpandedSubBlockEnd.gif        }

37InBlock.gif
38InBlock.gif        
39InBlock.gif
40ExpandedBlockEnd.gif    }

再次查看编译程序最终生成的运行时代码

 1None.gifpublic void add_NewEvent(EventHandler<DemoEventArgs> value)
 2ExpandedBlockStart.gifContractedBlock.gifdot.gif{
 3InBlock.gif    lock (this._eventLock)
 4ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
 5InBlock.gif        this.internalNewEvent = (EventHandler<DemoEventArgs>) Delegate.Combine(this.internalNewEvent, value);
 6ExpandedSubBlockEnd.gif    }

 7ExpandedBlockEnd.gif}

 8None.gif
 9None.gifpublic void remove_NewEvent(EventHandler<DemoEventArgs> value)
10ExpandedBlockStart.gifContractedBlock.gifdot.gif{
11InBlock.gif    lock (this._eventLock)
12ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
13InBlock.gif        this.internalNewEvent = (EventHandler<DemoEventArgs>) Delegate.Remove(this.internalNewEvent, value);
14ExpandedSubBlockEnd.gif    }

15ExpandedBlockEnd.gif}

16None.gif
17None.gif 
18None.gif
19None.gif 
20None.gif

从性能上考虑,当某个组件定义大量事件时,该实例会浪费大量的内存,之所以是浪费,是因为其中很多事件都没有被订阅。这一点,我们可以参考System.Web.UI.Control中的实现。首先在内部包含了一个EventHandlerList类型的Events属性。

None.gif protected EventHandlerList Events
ExpandedBlockStart.gifContractedBlock.gif    
dot.gif{
InBlock.gif        
get
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
this.EnsureOccasionalFields();
InBlock.gif            
if (this._occasionalFields.Events == null)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                
this._occasionalFields.Events = new EventHandlerList();
ExpandedSubBlockEnd.gif            }

InBlock.gif            
return this._occasionalFields.Events;
ExpandedSubBlockEnd.gif        }

ExpandedBlockEnd.gif    }

 

None.gif public event EventHandler DataBinding
ExpandedBlockStart.gifContractedBlock.gif    
dot.gif{
InBlock.gif        add
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
this.Events.AddHandler(EventDataBinding, value);
ExpandedSubBlockEnd.gif        }

InBlock.gif        remove
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            
this.Events.RemoveHandler(EventDataBinding, value);
ExpandedSubBlockEnd.gif        }

ExpandedBlockEnd.gif    }

引发事件

None.gif protected virtual void OnDataBinding(EventArgs e)
ExpandedBlockStart.gifContractedBlock.gif    
dot.gif{
InBlock.gif        
if (this.HasEvents())
ExpandedSubBlockStart.gifContractedSubBlock.gif        
dot.gif{
InBlock.gif            EventHandler handler 
= this._occasionalFields.Events[EventDataBinding] as EventHandler;
InBlock.gif            
if (handler != null)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
dot.gif{
InBlock.gif                handler(
this, e);
ExpandedSubBlockEnd.gif            }

ExpandedSubBlockEnd.gif        }

ExpandedBlockEnd.gif    }

这样实现自我感觉不是很好,由于EventHandlerList是一个链表,造成通过内部的find方法查找时,要遍历循环。更理想的做法是使用一个Hashtable来存储委托链。
(参考:CLR via C#)
 

转载于:https://www.cnblogs.com/DreamWinter/archive/2007/08/15/856666.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值