PG日誌

読者です 読者をやめる 読者になる 読者になる

PG日誌

主にc#の事を書いています

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

c#

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

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

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

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

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


f:id:Takachan:20160721155544p:plain

イベントってWPFとかだとたくさん呼ぶことがありますが、いちいちイベントにインスタンスが入っているかどうかを確認するとコードが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でなければ呼ばれます。できれば「??」演算子と一緒に実装してほしかったです。ハイ。

// イベントの宣言
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)
            {
                Console.WriteLine("デリゲートが未設定です。");
                return;
            }
            source.DynamicInvoke(args);
        }
        catch (Exception ex)
        {
            if(ex is ArgumentException || ex is TargetParameterCountException)
            {
               Console.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

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

ただ、引数が

param object[]

のせいで呼び出し時に型チェックが利かないんですよね。
全部にnullチェック書くよりマシかもしれませんが。

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

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、、、
パラメーターの型から全ての型推論を勝手にやってくれるおかげで呼び出すときも面倒が無くて助かります。

広告を非表示にする