C#のイベントのnullチェックがめんどくさい

イベントってWPFだとプロパティ変更を画面(View)に通知するために複数実装することがあります。ただ、その時いちいちイベントにインスタンスが入っているかどうかを確認するとコードがnullチェックだらけになってしまいます。

デリゲート呼び出し時のnullチェック

// イベントの宣言
public event Action<object, EventArgs> HogeEvent;

// イベント利用側のコード
public void Hogehoge()
{
    if(this.HogeEvent != null)
    {
        this.HogeEvent(this, new EventArgs());
    }
}

C# 6.0版

VisualStudio2015 以降で C# 6.0 が使えれば「Null条件演算子」が追加されているので以下のようにコードを記述できます。
「?」以降はインスタンスが null でなければ呼ばれますしそうでなければ呼ばれません。(できれば「??(Null合体)」演算子と一緒に実装してほしかったですね)

// イベントの宣言
public event Action<object, EventArgs> AnEvent;

// イベント利用側のコード
public void CallEvent()
{
    this.AnEvent?this.HogeEvent(this, new EventArgs());
}

C# 5.0までの書き方で処理を共通化

C#6.0~を利用できない環境の場合、このような定型的な処理を共通化すると以下のようになります。(さすがにもう無いか…

プロジェクトの状況を見て以下のようなコードを記述することになります。まず、以下の汎用の処理をDelegateUtilityに用意します。

public static class DelegateUtility
{
    // 指定したデリゲートを安全に実行する
    public static void RunSafe(Delegate source, params object[] args)
    {
        try
        {
            if (source == null)
            {
                Trace.WriteLine("デリゲートが未設定です。");
                return;
            }
            source.DynamicInvoke(args);
        }
        catch (Exception ex)
        {
            if(ex is ArgumentException || ex is TargetParameterCountException)
            {
               Trace.WriteLine(ex.ToString());
               return;
            }
            throw;
        }
    }
}

上記コードの使用方法です。

public class DelegateTest
{
    public static Action<int> d_1;
    public static Action<int, int> d_2;
    public static Action<int, int> d_3;
    public static EventHandler d_4;
    public static event Action<int> e_1;
    public static event Action<int> e_2;

    public static void Main(string[] args)
    {
        Console.WriteLine("Begin");

        d_1 = i => Console.WriteLine(i);
        d_2 = (__i, __j) => Console.WriteLine(__i + ", " + __j);
        
        d_4 = (__sender, __e) => 
            Console.WriteLine(__sender.GetType().Name + ", " + __e.GetType().Name);
            
        e_1 += __i => Console.WriteLine("e_1 Call " + __i);
        e_1 += __i => Console.WriteLine("e_2 Call" + __i);

        // 通常の Delegate の呼び出し
        DelegateUtility.RunSafe(d_1, 1);

        // 引数の型を間違えた
        DelegateUtility.RunSafe(d_1, "a");
        DelegateUtility.RunSafe(d_2, 2);

        // 自作の Delegate の呼び出し
        DelegateUtility.RunSafe(d_4, new object(), new EventArgs());

        // 引数の個数を間違えた
        DelegateUtility.RunSafe(d_1, 999, 9999);
        DelegateUtility.RunSafe(d_1, "a", "b");

        // 未設定の Delegate の呼び出し
        DelegateUtility.RunSafe(d_3, 3);

        // 通常の event の呼び出し
        DelegateUtility.RunSafe(e_1, 10);

        // 複数個登録されている event の呼び出し
        DelegateUtility.RunSafe(e_2, 10);

        // 未設定の event の呼び出し
        DelegateUtility.RunSafe(e_1, 10);

        Console.WriteLine("End");
        Console.ReadLine();
    }
}

上記の実行結果ですが以下の通りになります。

Begin
1
System.ArgumentException: 
型 'System.String' のオブジェクトを型 'System.Int32' に変換できません。
...

System.Reflection.TargetParameterCountException: 
パラメーター カウントが一致しません。
...

Object, EventArgs

System.Reflection.TargetParameterCountException: 
パラメーター カウントが一致しません。
...

System.Reflection.TargetParameterCountException: 
パラメーター カウントが一致しません。
...

デリゲートが未設定です。
e_1 Call 10
e_2 Call10
デリゲートが未設定です。
e_1 Call 10
e_2 Call10
End

変数値が未設定の場合と、パラメータの型と個数が一致していないと例外が発生して実行されず安全に実行可能です。

ただ、引数が

RunSafe(Delegate source, params object[] args/*objectの配列!*/)
 ||<

object配列のため、呼び出し時に型チェックが効いていません。渡し間違えると例外になってしまいます。
但し、全部にnullチェック書くよりマシかもしれませんが。

非同期の delegate の時はどうするんでしょうね?
今のところ用が無いので確認してませんがそうした場合は、

>|cs|
public static async Task RunSafeAsync(Delegate source, params object[] args)

を用意して

//source.DynamicInvoke(args);
await Task.Run(() => source.DynamicInvoke(args));

と、すれば動くと思います。(多分…

一応これで作っておいて以降で C# 6.0 に移行したら一括で置き換えしても良さそうですね。