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

PG日誌

プログラミングの事とか。主にc#。

c#のeventとdelegateの違い

c# 技術ネタ

マウスをクリックした場合などに発生する"イベント"の話ではなく、c#予約語の"event構文"の話です。

f:id:Takachan:20160721155544p:plain

本当にざっくりとした先に結論を書くと、"event構文"は"delegate専用のプロパティ"の一種です。

delegate型をクラス外に公開する場合だけ特別に、"プロパティ構文で公開する"と"event構文"で公開する2種類が選べます。

以下のように自動実装プロパティのように宣言できます。

public class DelegateEvent
{
    // 普通のプロパティで公開する(自動実装)
    public Action<object, EventArgs> PropDelegate { get; set; }

    // event構文で公開する(自動実装)
    public event Action<object, EventArgs> HogeEvent;
}

普通のプロパティで公開した場合デリゲートに対して全ての操作ができるのですが、event構文を使用して公開した場合、利用者からはメソッドの登録と登録解除しかできなくなります。(自クラス内だとデリゲートのように使用できます)

public void Hoge(DelegateEvent source)
{
    // デリゲートのプロパティの操作

    // 自分のメソッドを設定
    source.PropDelegate = this.AnHandler;
    // 自分のメソッドを追加
    source.PropDelegate += this.AnHandler;
    // 登録内容を全部削除
    source.PropDelegate = null;
    // デリゲートを呼び出す(この場合は例外)    
    source.PropDelegate(this, new EventArgs);

    // イベントの操作

    // 自分のメソッドを設定
    source.HogeEvent= this.AnHandler; // ★「できない
    // 自分のメソッドを追加
    source.HogeEvent+= this.AnHandler;
    // 登録内容を全部削除
    source.HogeEvent= null; // ★できない
    // デリゲートを呼び出す(この場合は例外)    
    source.HogeEvent(this, new EventArgs); // ★できない
}

// デリゲートやイベントに設定するメソッド
public void AnHandler(object sender, EventArgs e) { ... }

なので、メソッドの登録解除しかさせたくない場合(オブザーバーパターン的に利用したい場合)event構文を付け加えてデリゲートを宣言します。

重要なのは、event構文を用いた場合、設定元から source.HogeEvent(this, new EventArgs); のようにメソッド呼び出しできなくなり、呼び出せるのは設定したクラス内からのみとなります。

なので「メソッドを運搬したい」や、「ある処理の述語に使用したい」、「デリゲートの終了を待って後続のシーケンスのが継続する」場合普通のプロパティで大丈夫です。

逆に通知しっぱなしの場合、eventでいいとも言えます。

自動実装じゃない場合、以下のように宣言します。

public class DelegateEvent
{
    // 普通のプロパティで公開する
    private Action<object, EventArgs> _PropDelegate
    public Action<object, EventArgs> PropDelegate
    {
        get { return this._PropDelegate; }
        set { this._PropDelegate = value; }
    }

    // event構文で公開する
    public Action<object, EventArgs> _HogeEvent; // ただのデリゲート宣言
    public event Action<sender, MyEventArgs> HogeEvent
    {
        add { this._HogeEvent+= value; }
        remove { this._HogeEvent-= value; }
        // +=, -=でメソッドを複数登録できるようにしておく(お約束)
    }
}

「オブザーバーパターン」の説明はGoFデザインパターンを説明しているサイトがいっぱいあるのでそちらを参照してもらって使い分け方はざっくりと

1. 呼んでほしいメソッドの登録、登録解除しか{させたくない|したくない}場合event構文を使う
2. そうではなくもっといろいろしたい(設定したクラス外からも使う)場合プロパティを使う

かと思います。

補足ですが、event構文を単純に抜いただけだとフィールドを直接公開するだけになってしまうので、eventをとった時は後ろに{ get; set; }を付けるようにしましょう。

// これはよくない

// イベント構文をつけないでデリゲートをそのまま公開
//  => フィールド直接公開になっちゃう
public Action<object, EventArgs> HogeEvent;

ObservableCollectionにAddRangeを追加する

タイトルの通りなんですが、ObservableCollectionやIListにはAddRangeが無いんですよね。WPFでObservableCollectionの入れ替えを毎回foreachでやってたら憤死しそうにりました。けっこう前から結構不便に感じていたのでライブラリ作りました。

f:id:Takachan:20160721155544p:plain

こんな感じです。

/// <summary>
/// 配列用の汎用機能を提供します。
/// </summary>
public static class ArrayUtility
{
    /// <summary>
    /// 指定したコレクションを全て格納します。
    /// </summary>
    public static void AddRange<T>(this ICollection<T> collection, ICollection<T> target)
    {
        foreach (var item in target)
        {
            collection.Add(item);
        }
    }

    /// <summary>
    /// 指定したコレクションを変換して格納します。
    /// </summary>
    public static void AddRange<T, S>(this ICollection<T> collection, ICollection<S> target, Func<S, T> convert)
    {
        foreach (var item in target)
        {
            collection.Add(convert(item));
        }
    }
}

同じ型同士をAddRangeする場合と、違う型へ変換する場合、最後に変換規則を指定して呼び出します。
Listの拡張メソッドと名前が完全にぶつかる気がしますが、どうなるんでしょうね?(汗)

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

クラリネットの運指表

音楽

クランポンの楽器を買ったときについていたクラリネットの運指表が横に長すぎて取り回しが悪かったのでA4サイズに加工してみました。印刷して練習本の表紙に挟んでおくといいかもしれません。

この運指表、多分どのサイトに掲載されているのよりも詳しいと思います。
(掲載に問題があったら取り下げます)

ビュッフェ・クラン本株式会社発行
パリ音楽院教授
ギィ・ドゥプリュ監修

f:id:Takachan:20160509224020p:plain


この音階練習本やるときに(たぶん)役に立ちます。特に高音域で。

参考画像

ちなみにこの2巻、1ページ目から本気で襲い掛かってくるのでご注意を…

f:id:Takachan:20160509225044j:plain

cocos2d-xのUSING_NS_CCについて

c/c++ 技術ネタ cocos2d-x

cocos2d-xのUSING_NS_CCはCCPlatformMacros.hに以下のように定義されています。

#ifdef __cplusplus
    #define NS_CC_BEGIN   namespace cocos2d {
    #define NS_CC_END     }
    #define USING_NS_CC   using namespace cocos2d
    #define NS_CC         ::cocos2d
#else
    #define NS_CC_BEGIN 
    #define NS_CC_END 
    #define USING_NS_CC 
    #define NS_CC
#endif

c++で開発するのでこの行が宣言になります。

#define USING_NS_CC using namespace cocos2d

大抵のクラスが見つからないエラーは#inclideの直後にUSING_NS_CCって書けば収まります。

が、ただ、この宣言をマクロで定義する意味ってあるんですかね・・・?

後で変わる可能性がある説

cocos2dという名前空間が後から変わる事がある、もしくはその予定があるって事だったらそんな変更するなよと言いたい。
むしろc++じゃない言語(つまりC言語?)で開発しない(できない)のでは・・・?(そしてc++じゃないときはUSING_NS_CCって空宣言で意味ないじゃん)

タイピング数を減らす説

ただ単にそのほうがタイピング文字数が少ないでしょって事なのかしら?

それで?

もし後者だったら標準の構文の言い換えに過ぎません。

ただ単に覚えることが余計に1つ増えるだけだし、標準の構文をマクロで置き換えるのはわりとやっちゃいけないことの部類なのでむしろ有害です。

普通に using namespace cocos2d って書いたほうがよいと思います。