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# 5.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();
    }
}

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

> |cs|
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 に移行したら一括で置き換えしても良さそうですね。

お詫び

すいません。以前載せてたのが全然解決になっていませんでした。
これだと、扱う全ての型をクラスに宣言しないといけないので余計めんどくさくなっていました。

// 共通処理部分
public static class DelegateUtility
{
    // イベントのnullチェックをしてnullでなければ実行
    public static void Call<P1, P2>(Action<P1, P2> source, P1 p1, P2 p2)
    {
        if(source == null)
        {
            return;
        }
        source(p1, p2);
    }
}

// 利用側のコード

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

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


というか、さすがVisualStudio、、、
パラメーターの型から全ての型推論を勝手にやってくれるおかげで呼び出すときも面倒が無くて助かります。