Chapter 1 - Item 8 : Use the Null Conditional Operator for Event Invocations

Effective C# (Covers C# 6.0), (includes Content Update Program): 50 Specific Ways to Improve Your C#, 3rd Edition By Bill Wagner 讀後心得

C# 6.0 推出檢查事件是否為空值的簡潔新語法:?.運算子;以下用範例程式演示。

寫法一:

private event EventHandler<int> OnSomething_Updated;
private int _counter;

public void raiseUpdates( )
{
    _counter++;

    // throw NullReferenceException when no event handlers have been   
    // attached to the OnSomething_Updated event.
    OnSomething_Updated( this, _counter ); 
}

寫法一顯然不夠好,並沒有把邊界條件考慮進去。

寫法二:

private event EventHandler<int> OnSomething_Updated;
private int _counter;

public void raiseUpdates2( )
{
    _counter++;
    
    // throw NullReferenceExecption when another thread unsubscribe the 
    // single event handler at 2.
    if ( OnSomething_Updated != null ) // 1. Check if null.
        OnSomething_Updated( this, _counter ); // 2. Execute.
}

寫法二雖然加入了事件是否為空值的判斷,但並不是執行緒安全;仍有改進空間。

寫法三:

private event EventHandler<int> OnSomething_Updated;
private int _counter;

public void raiseUpdates3( )
{
    _counter++;
    var handler = OnSomething_Updated;

    if ( handler != null )
        handler( this, _counter );
}

寫法三使用區域變數 handler 記錄事件方法(invocation list)的記憶體位置副本。由於 Delegate 為 immutable,執行事件訂閱或解除訂閱,都會創建新的 invocation list;即便在觸發 handler 事件方法前別條執行緒解除訂閱 OnSomething_Update,並不會改變 handler。

寫法四(C# 6.0 以後可用):

private event EventHandler<int> OnSomething_Updated;
private int _counter;

public void raiseUpdates4( )
{
    _counter++;

    OnSomething_Updated?.Invoke( this, _counter );
}

寫法四背後的操作邏輯其實和寫法三相同,?. 判斷為執行緒安全的操作;差異在程式碼更加簡潔,且不用針對判斷事件是否空值撰寫重覆的程式碼。

結論:
1. 事件 ?. 運算子後面只能接 Invoke,底層會自動產生對應事件型別的程式碼。

2. 實務上,使用 ?. 運算子更加方便;但第三種寫法背後的原理應該要了解。

參考資料: 
Events and Races
Delegate 類別
節錄:The invocation list of a delegate is an ordered set of delegates in which each element of the list invokes exactly one of the methods represented by the delegate. An invocation list can contain duplicate methods. During an invocation, methods are invoked in the order in which they appear in the invocation list. A delegate attempts to invoke every method in its invocation list; duplicates are invoked once for each time they appear in the invocation list. Delegates are immutable; once created, the invocation list of a delegate does not change.