代码改变世界

C# in depth: Delegates and Events[读书笔记加翻译]

2011-08-15 22:53  一一九九  阅读(428)  评论(0编辑  收藏  举报

http://csharpindepth.com/Articles/Chapter2/Events.aspx?printable=true

A shortcut: field-like events

  默认的Event声明方法如下:
     public event EventHandler MyEvent;
这种方法创建了一个delegate 变量和event.

当然也可以明确的指明Add/remove方法,但是这种方法需要加一下锁,代码如下:

private EventHandler _myEvent;
    
public event EventHandler MyEvent
{
    add
    {
        lock (this)
        {
            _myEvent += value;
        }
    }
    remove
    {
        lock (this)
        {
            _myEvent -= value;
        }
    }        
} 

这种方法是为实例对象而设的,对于一个声明为static的event来说,变量也是static并且Lock应该在typeof(xxx)上执行。对于什么用来执行Lock,在C#2.0中有了简要的保证:只有一个实例对象用于实例的event, 一个和类关联的对象用于static events的锁定。

Thread-safe events

       假如你需要彻底的thread-safe, 那么当你触发一个时间的时候需要使用最新的Delegate的变量值,确保add/remove操作相互之间不影响,需要自己来实现add/remove方法。下面是一个示例:

/// <summary>
/// Delegate variable backing the SomeEvent event.
/// </summary>
SomeEventHandler someEvent;
/// <summary>
/// Lock for SomeEvent delegate access.
/// </summary>
readonly object someEventLock = new object();
/// <summary>
/// Description for the event
/// </summary>
public event SomeEventHandler SomeEvent
{
    add
    {
        lock (someEventLock)
        {
            someEvent += value;
        }
    }
    remove
    {
        lock (someEventLock)
        {
            someEvent -= value;
        }
    }
}
/// <summary>
/// Raises the SomeEvent event
/// </summary>
protected virtual void OnSomeEvent(EventArgs e)
{
    SomeEventHandler handler;
    lock (someEventLock)
    {
        handler = someEvent;
    }
    if (handler != null)
    {
        handler (this, e);
    }
} 

     可以所有的事件都采用一个lock, 也可以和其他的所有事情都采用一个lock. 需要确保当前赋值给私有变量的操作在Lock中,然后测试是否为空,然后在Lock的外边执行它。 当触发事件的时候锁定HandlerList是比较糟糕的事情,很容易一起来死锁(Event Handler 可能同时需要等待另外一个线程做某件事情,假如另外一个线程正在调用事件的Add或则Remove方法,此时得到了一个死锁)

       如果不需要Event的Thread-safe,则可以采取以下的代码:

/// <summary>
/// Delegate variable backing the SomeEvent event.
/// </summary>
SomeEventHandler someEvent;
/// <summary>
/// Description for the event
/// </summary>
public event SomeEventHandler SomeEvent
{
    add
    {
        someEvent += value;
    }
    remove
    {
        someEvent -= value;
    }
}
/// <summary>
/// Raises the SomeEvent event
/// </summary>
protected virtual OnSomeEvent(EventArgs e)
{
    if (someEvent != null)
    {
        someEvent (this, e);
    }
} 

在这里有人提到Event的两种写法:

http://codereview.stackexchange.com/questions/1142/checking-if-an-event-is-not-null-before-firing-it-in-c

I often see this for custom events:

void InvokeCustomEvent(EventArgs e)
{
    var handler = CustomEvent;
    if (handler != null) handler(this, e);
}

But is creating the handler variable required, best practice, or superfluous, when compared to:

void InvokeCustomEvent(EventArgs e)
{
    if (CustomEvent != null) CustomEvent(this, e);
}
回复是:

Yes, it is needed. Otherwise, CustomEvent could be set to null after you've checked for null, but before you've invoked it. This can happen if it gets set in another thread, or if one of the event handlers unregisters itself or another one.

I usually have this declared somewhere in my codebase:

public static class EventExtensions
{
    public static void Raise<T>(this EventHandler<T> handler, T args) {
        if (handler != null) handler(args);
    }
}

Now, with any event handler, you can just do:

handler.Raise(args);

You can call an extension method with a null this, so it will do the right thing even if handler is null. Furthermore, by copying handler into the local variable used by Raise, you automatically get the copy you need to make sure the handler won't disappear under you. Works like a charm.

    

触发事件的几个判断方法
http://www.redirecttonull.com/?p=73

      事件触发的时候需要判断为NULL,所以存在事件触发的几种模式,大体上如下所示:

he following code snippet describes how to raise an event, and is taken directly from the MSDN Developers Guide:

public event EventHandler Alarm;
protected virtual void OnAlarm(EventArgs e)
{
if (Alarm != null)
{
Alarm(this, e);
}
}
This pattern has a critical floor. If, after the Alarm != null check another thread removes the last handler a NullReferenceException will occur. Fortunately JayBaz over at MSDN proposes two solutions.

Solution 1

The first solution is to maintain a local reference to the event. In this way the null check and invoke are guaranteed to be performed on the same object.

public event EventHandler Alarm;
protected virtual void OnAlarm(EventArgs e)
{
EventHandler local = Alarm;
if (local != null)
{
local(this, e);
}
}

Solution 2

The second solution is to create an empty handler so that the event will never be null.

public event EventHandler Alarm = delegate { };
protected virtual void OnAlarm(EventArgs e)
{
Alarm(this, e);
}

Both of these solutions are fairly unobtrusive, and quick to implement. However, I can’t help wondering how this was missed by the guys at Microsoft in the first place, and also why the MSDN Developer Guide has not been updated. The fact Microsoft provide the event keyword just shows how important this pattern is to them, so I’m left feeling a little confused.