かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

イケテルINotifyPropertyChangedの実装の改善

2010年になってから6日もたって初のBlog更新になります。新年あけましておめでとうございます。今年も、マイペースですが試したことや、思ったこと、後ほんの少しの日常を綴っていこうと思います。
今年もよろしくお願いします。

さて、今回の記事のネタは以下の記事が元になってます。
ネタ元記事

意外というか予想より、はるかに遅かったINotifyPropertyChangedのPropertyChangedイベントの発行処理ですが、コメントに

ななしさんのコメントから引用
--
senderはこれでも取得できるみたいです。
var sender = senderExpression.Value;
デバッグ実行で表示される値を眺めてて見つけました。

とのタレコミがありました。多謝です!!ConstantExpressionは、Valueプロパティで値がとれるということみたいです。

MSDNにも

表された式の値に等しい Object。

と説明にあるのでDelegateに変換してDynamicInvokeするまでもなかったみたいです。
ということで、改良版のPropertyChangedEventHandlerExtensionsは以下のようになります。

public static class PropertyChangedEventHandlerExtensions
{
    /// <summary>
    /// イベントを発行する
    /// </summary>
    /// <typeparam name="TResult">プロパティの型</typeparam>
    /// <param name="_this">イベントハンドラ</param>
    /// <param name="propertyName">プロパティ名を表すExpression。() => Nameのように指定する。</param>
    public static void Raise<TResult>(this PropertyChangedEventHandler _this,
        Expression<Func<TResult>> propertyName)
    {
        // ハンドラに何も登録されていない場合は何もしない
        if (_this == null) return;

        // ラムダ式のBodyを取得する。MemberExpressionじゃなかったら駄目
        var memberEx = propertyName.Body as MemberExpression;
        if (memberEx == null) throw new ArgumentException();

        // () => NameのNameの部分の左側に暗黙的に存在しているオブジェクトを取得する式をゲット
        var senderExpression = memberEx.Expression as ConstantExpression;
        // ConstraintExpressionじゃないと駄目
        if (senderExpression == null) throw new ArgumentException();

        // ×:式を評価してsender用のインスタンスを得る
        // var sender = Expression.Lambda(senderExpression).Compile().DynamicInvoke();
        // ○:定数なのでValueプロパティからsender用のインスタンスを得る
        var sender = senderExpression.Value;

        // 下準備が出来たので、イベント発行!!
        _this(sender, new PropertyChangedEventArgs(memberEx.Member.Name));
    }
}

これで、10000回実行して比較してみました。

実装方法 時間
通常 13ms
性能悪いイケテル方法 3593ms
今回のイケテル方法 92ms

結構普通の方法に迫ってきたかも。memberEx.Member.Nameでプロパティ名を取得してるところのオーバーヘッドがほとんどなんだろうなぁ。
ここは、どうしようもないのだろうか?う〜ん。