INotifyPropertyChangedの実装-より良い方法はありますか?


647

MicrosoftはINotifyPropertyChanged、自動プロパティのように、何かにきびきびとしたものを実装する必要がありました。指定するだけで、{get; set; notify;} それを実行するのは理にかなっていると思います。それとも、それを行うには複雑さがありますか?

私たちは自分のプロパティに「通知」のようなものを実装できますか?INotifyPropertyChangedクラスに実装するための優雅な解決策はありますか、それを行う唯一の方法はPropertyChanged、各プロパティでイベントを発生させることです。

できなければ、PropertyChanged イベントを発生させるコードを自動生成する何かを書くことができますか?



7
上記のリンクは死んでいます。github.com/SimonCropp/NotifyPropertyWeaver
プライム

2
代わりにDependencyObjectとDependencyPropertiesを使用できます。ハ!面白かった。
Phil


5
相互依存関係の膨大なバックログがあったため、当時はC#に変更を加えることはできませんでした。MVVMが生まれた頃は、この問題の解決にあまり力を入れていませんでした。パターン&プラクティスチームが途中でいくつかの取り組みを行ったことがわかっています(したがって、その一部としてMEFも入手しました)研究スレッド)。今日、私は[CallerMemberName]が上記の答えだと思います。
Scott Barnes

回答:


633

postharpのようなものを使用せずに、私が使用する最小バージョンは次のようなものを使用します。

public class Data : INotifyPropertyChanged
{
    // boiler-plate
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
    protected bool SetField<T>(ref T field, T value, string propertyName)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    // props
    private string name;
    public string Name
    {
        get { return name; }
        set { SetField(ref name, value, "Name"); }
    }
}

各プロパティは次のようになります。

    private string name;
    public string Name
    {
        get { return name; }
        set { SetField(ref name, value, "Name"); }
    }

それは巨大ではありません。必要に応じて、基本クラスとして使用することもできます。他のロジックを適用したい場合に備えて、boolからの戻りはSetFieldそれが何もしなかったかどうかを教えてくれます。


またはC#5でさらに簡単:

protected bool SetField<T>(ref T field, T value,
    [CallerMemberName] string propertyName = null)
{...}

これは次のように呼び出すことができます:

set { SetField(ref name, value); }

コンパイラが"Name"自動的に追加します。


C#6.0は実装を容易にします。

protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

...そしてC#7で:

protected void OnPropertyChanged(string propertyName)
   => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

protected bool SetField<T>(ref T field, T value,[CallerMemberName] string propertyName =  null)
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(propertyName);
    return true;
}

private string name;
public string Name
{
    get => name;
    set => SetField(ref name, value);
}

4
ニーストリックマーク!プロパティ名の代わりにラムダ式を使用するように改善を提案しました。私の回答を参照してください
Thomas Levesque '22

7
@トーマス-ラムダはすべてうまくいっていますが、実際には非常に単純なものに対して多くのオーバーヘッドが追加されます。便利なトリックですが、常に実用的であるかどうかはわかりません。
Marc Gravell

14
@Marc-はい、おそらくパフォーマンスが低下する可能性があります...しかし、コンパイル時にチェックされ、「Rename」コマンドによって正しくリファクタリングされるという事実が本当に気に入っています
Thomas Levesque

4
@Gusdor幸い、C#5では妥協する必要はありません-(Pedro77のメモのように)両方の利点を最大限に活用できます[CallerMemberName]
Marc Gravell

4
@Gusdor言語とフレームワークは別々です。C#5コンパイラを使用し、.NET 4をターゲットにして、不足している属性を自分で追加するだけで十分です。正しい名前を持ち、正しい名前空間にある必要があります。特定のアセンブリ内にある必要はありません。
Marc Gravell

196

.Net 4.5以降、これを行う簡単な方法がついに登場しました。

.Net 4.5では、新しい発信者情報属性が導入されています。

private void OnPropertyChanged<T>([CallerMemberName]string caller = null) {
     // make sure only to call this if the value actually changes

     var handler = PropertyChanged;
     if (handler != null) {
        handler(this, new PropertyChangedEventArgs(caller));
     }
}

関数に比較子を追加することもおそらく良い考えです。

EqualityComparer<T>.Default.Equals

ここここのより多くの例

呼び出し元情報(C#およびVisual Basic)も参照してください。


12
鮮やかさ!しかし、なぜそれがジェネリックなのでしょうか?
abatishchev

@abatishchev私はそれがそうである必要はないと思います、私は関数にプロパティも設定させるという考えで遊んでいました。答えを更新して完全なソリューションを提供できるかどうかを確認します。余分な例は、それまでの間はうまくいきます。
Daniel Little

3
C#5.0で導入されました。.net 4.5とは関係ありませんが、これは素晴らしい解決策です!
J.レノン

5
@J。Lennon .net 4.5は、すべての属性がどこかから取得さ
Daniel Little

@Lavinskiはアプリケーションを.NET 3.5などに変更し、何が機能するかを確認します(vs2012で)
J. Lennon

162

私はMarcのソリューションが本当に好きですが、「マジックストリング」(リファクタリングをサポートしていない)を使用しないように少し改善できると思います。プロパティ名を文字列として使用する代わりに、ラムダ式にするのは簡単です。

private string name;
public string Name
{
    get { return name; }
    set { SetField(ref name, value, () => Name); }
}

Marcのコードに次のメソッドを追加するだけで、トリックが実行されます。

protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
{
    if (selectorExpression == null)
        throw new ArgumentNullException("selectorExpression");
    MemberExpression body = selectorExpression.Body as MemberExpression;
    if (body == null)
        throw new ArgumentException("The body must be a member expression");
    OnPropertyChanged(body.Member.Name);
}

protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(selectorExpression);
    return true;
}

ところで、これはこのブログ投稿の 更新されたURLに触発されました


6
このメソッドを使用するフレームワークが少なくとも1つ、ReactiveUIです。
AlSki、2011年

非常に遅く、これはリフレクションを通過することを意味し、パフォーマンスの打撃を意味しました。許容できるかもしれませんが、プロパティを設定することは、アプリケーションに多くのサイクルを費やす場所ではありません。
ブルーノブラント

1
@BrunoBrantパフォーマンスに影響があることは確かですか?ブログの投稿によると、リフレクションは実行時ではなくコンパイル時に発生します(つまり、静的リフレクション)。
Nathaniel Elkins 2015年

6
OnPropertyChanged <T>全体がC#6のnameof演算子で廃止されていると思いますが、このモンスターは少し洗練されています。
Traubenfuchs

5
@Traubenfuchs、実際には、C#5のCallerMemberName属性を使用すると、何も渡す必要がないため、さらに簡単になります...
Thomas Levesque

120

PropertyChangedアドインを持つFodyもあります。これにより、次のように記述できます。

[ImplementPropertyChanged]
public class Person 
{        
    public string GivenNames { get; set; }
    public string FamilyName { get; set; }
}

...そしてコンパイル時にプロパティ変更通知を挿入します。


7
これは、OPが「プロパティに 'notify'のようなものを自分で実装できますか?クラスにINotifyPropertyChangedを実装するための優雅なソリューションはありますか?」と尋ねたときに、まさにこれを探していたと思います
Ashoat

3
これは本当に優雅な唯一のソリューションであり、@ CADblokeが言ったように問題なく機能します。ウィーバーにも懐疑的でしたが、背後にあるILコードをチェック/再チェックしました。完璧で、シンプルで、必要なことをすべて実行し、他には何もしません。また、NotifyOnProp ...、OnNotify ...が問題ではないかどうかに関係なく、基本クラスで指定したメソッド名をフックして呼び出します。そのため、INotifyを実装している可能性のある基本クラスでうまく機能します。 。
NSGaga-ほとんどが不活性

1
ウィーバーが何をしているかを簡単に再確認し、ビルド出力ウィンドウを見て、ウィーバーが織り込んだすべてのPropertyChangedをリストすることができます。正規表現パターンでVScolorOutput拡張を使用する"Fody/.*?:",LogCustom2,Trueと、「カスタム2」色で強調表示されます。見つけやすいように鮮やかなピンクにしました。すべてをFodyするだけで、繰り返し入力することが多いものを実行する最も簡単な方法です。
CADは17

@mahmoudnezarsarhanいいえ、そうではありません。構成方法に若干の変更があったことを覚えていますが、Fody PropertyChangedはまだアクティブでアクティブです。
ラリー

65

人々はパフォーマンスにもう少し注意を払うべきだと思います。バインドするオブジェクトが多数ある場合(10,000行以上のグリッドを考える)、またはオブジェクトの値が頻繁に変更される場合(リアルタイム監視アプリ)、UIに実際に影響します。

私はここや他の場所で見つかったさまざまな実装を取り、比較を行いました。INotifyPropertyChanged実装のパフォーマンス比較を確認してください。


これが結果をのぞきます 実装とランタイム


14
-1:パフォーマンスのオーバーヘッドはありません。CallerMemberNameはコンパイル時にリテラル値に変更されます。アプリを逆コンパイルしてみてください。
JYL 2014年

ここに応じて質問と答えは次のとおりです。stackoverflow.com/questions/22580623/...
uli78

1
@JYL、あなたはCallerMemberNameが大きなオーバーヘッドを追加しなかったことは正しいです。前回試したとき、何か間違ったことを実装したに違いありません。CallerMemberNameとFody実装のベンチマークを反映するために、ブログと回答を後で更新します。
Peijen 14

1
あなたはUIで10,000のグリッドを持っているなら、あなたはおそらく...あなただけのページごとに10、50、100、250安打を示しページングのように、ハンドルのパフォーマンスへのアプローチを組み合わせることでなければなりません
オースティンRhymer

オースティンライマー、ラージデータと50のデータ仮想化を使用している場合、すべてのデータをロードする必要はありません。現在のスコアリング表示領域に表示されているデータのみがロードされます。
ビラル

38

Bindableクラスをブログ(http://timoch.com/blog/2013/08/annoyed-with-inotifypropertychange/)で 紹介しています。Bindableは、プロパティバッグとしてディクショナリを使用します。refパラメータを使用してサブクラスが独自のバッキングフィールドを管理するために必要なオーバーロードを追加するのは簡単です。

  • マジックストリングなし
  • 反射なし
  • デフォルトの辞書検索を抑制するように改善できます

コード:

public class Bindable : INotifyPropertyChanged {
    private Dictionary<string, object> _properties = new Dictionary<string, object>();

    /// <summary>
    /// Gets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    protected T Get<T>([CallerMemberName] string name = null) {
        Debug.Assert(name != null, "name != null");
        object value = null;
        if (_properties.TryGetValue(name, out value))
            return value == null ? default(T) : (T)value;
        return default(T);
    }

    /// <summary>
    /// Sets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="name"></param>
    /// <remarks>Use this overload when implicitly naming the property</remarks>
    protected void Set<T>(T value, [CallerMemberName] string name = null) {
        Debug.Assert(name != null, "name != null");
        if (Equals(value, Get<T>(name)))
            return;
        _properties[name] = value;
        OnPropertyChanged(name);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

次のように使用できます。

public class Contact : Bindable {
    public string FirstName {
        get { return Get<string>(); }
        set { Set(value); }
    }
}

2
これは良い解決策ですが、唯一の欠点は、ボクシング/アンボクシングを含む小さなパフォーマンスヒットがあることです。
MCattle 2014年

1
Set を使用protected T Get<T>(T defaultValue, [CallerMemberName] string name = null)してチェックインすることをお勧めしますif (_properties.ContainsKey(name) && Equals(value, Get<T>(default(T), name)))(最初にデフォルト値に設定したときにレイズして保存するため)
Miquel

1
カスタムデフォルト値のサポートを追加する@Miquelは確かに役立ちますが、値が実際に変更されたときにのみ変更イベントを発生させるように注意する必要があります。プロパティを同じ値に設定しても、イベントは発生しません。ほとんどの場合それは無害であることを認めなければなりませんが、UIの応答性を破壊するイベントによってプロパティが何千回も同じ値に設定されていることはかなりの数あります。
TiMoch

1
@stakx私はNHibernateのが使用可能でない場合、この上のビルドはアンドゥ/リドゥ用Mementoパターンをサポートするために、またはアプリケーションでの作業パターンの単位を有効にすることをいくつかのアプリケーションを持っている
TiMoch

1
私はこの特定のソリューションが本当に好きです:短い表記、動的なプロキシの要素、IL-meddlingなどはありません。ただし、Getを動的に返すことで、Getに毎回Tを指定する必要をなくすことで、短くすることができます。これは実行時のパフォーマンスに影響しますが、ゲッターとセッターのコードが最終的に常に同じになり、1行で主をたたえます!PS値型のデフォルト値を動的として返す場合は、Getメソッド内で(基本クラスを作成するときに1回)追加の注意を払う必要があります。常に正しいデフォルト値を返すようにしてください(それは可能です)
evilkos

15

実際にこれを試す機会はまだありませんでしたが、次にINotifyPropertyChangedの大きな要件を持つプロジェクトを設定するときに、コンパイル時にコードを挿入するPostsharp属性を作成するつもりです。何かのようなもの:

[NotifiesChange]
public string FirstName { get; set; }

となります:

private string _firstName;

public string FirstName
{
   get { return _firstname; }
   set
   {
      if (_firstname != value)
      {
          _firstname = value;
          OnPropertyChanged("FirstName")
      }
   }
}

これが実際に機能するかどうかわからないので、座って試してみる必要がありますが、なぜそうなのかわかりません。複数のOnPropertyChangedをトリガーする必要がある状況では、いくつかのパラメーターを受け入れるようにする必要がある場合があります(たとえば、上記のクラスにFullNameプロパティがある場合)。

現在、Resharperでカスタムテンプレートを使用していますが、それでも非常に長いすべてのプロパティにうんざりしています。


ああ、簡単なGoogle検索(これを書く前に行っておくべきだった)は、少なくとも1人の人がここまでにこのようなことをしたことを示しています。私が思い描いていたものとは正確には違いますが、理論が良いことを示すのに十分近いです。


6
Fodyと呼ばれる無料のツールも同じように動作し、汎用のコンパイル時コードインジェクターとして機能します。Nugetでダウンロードでき、PropertyChangedおよびPropertyChangingプラグインパッケージも同様です。
Triynko 14

11

はい、確かにより良い方法が存在します。ここにあります:

ステップバイステップのチュートリアルは、この便利な記事に基づいて、私によって縮小されました。

  • 新しいプロジェクトを作成する
  • プロジェクトにCastleコアパッケージをインストールする

インストールパッケージCastle.Core

  • mvvm lightライブラリのみをインストールする

インストールパッケージMvvmLightLibs

  • プロジェクトに2つのクラスを追加します。

NotifierInterceptor

public class NotifierInterceptor : IInterceptor
    {
        private PropertyChangedEventHandler handler;
        public static Dictionary<String, PropertyChangedEventArgs> _cache =
          new Dictionary<string, PropertyChangedEventArgs>();

        public void Intercept(IInvocation invocation)
        {
            switch (invocation.Method.Name)
            {
                case "add_PropertyChanged":
                    handler = (PropertyChangedEventHandler)
                              Delegate.Combine(handler, (Delegate)invocation.Arguments[0]);
                    invocation.ReturnValue = handler;
                    break;
                case "remove_PropertyChanged":
                    handler = (PropertyChangedEventHandler)
                              Delegate.Remove(handler, (Delegate)invocation.Arguments[0]);
                    invocation.ReturnValue = handler;
                    break;
                default:
                    if (invocation.Method.Name.StartsWith("set_"))
                    {
                        invocation.Proceed();
                        if (handler != null)
                        {
                            var arg = retrievePropertyChangedArg(invocation.Method.Name);
                            handler(invocation.Proxy, arg);
                        }
                    }
                    else invocation.Proceed();
                    break;
            }
        }

        private static PropertyChangedEventArgs retrievePropertyChangedArg(String methodName)
        {
            PropertyChangedEventArgs arg = null;
            _cache.TryGetValue(methodName, out arg);
            if (arg == null)
            {
                arg = new PropertyChangedEventArgs(methodName.Substring(4));
                _cache.Add(methodName, arg);
            }
            return arg;
        }
    }

ProxyCreator

public class ProxyCreator
{
    public static T MakeINotifyPropertyChanged<T>() where T : class, new()
    {
        var proxyGen = new ProxyGenerator();
        var proxy = proxyGen.CreateClassProxy(
          typeof(T),
          new[] { typeof(INotifyPropertyChanged) },
          ProxyGenerationOptions.Default,
          new NotifierInterceptor()
          );
        return proxy as T;
    }
}
  • たとえば、ビューモデルを作成します。

-

 public class MainViewModel
    {
        public virtual string MainTextBox { get; set; }

        public RelayCommand TestActionCommand
        {
            get { return new RelayCommand(TestAction); }
        }

        public void TestAction()
        {
            Trace.WriteLine(MainTextBox);
        }
    }
  • バインディングをxamlに入れます。

    <TextBox Text="{Binding MainTextBox}" ></TextBox>
    <Button Command="{Binding TestActionCommand}" >Test</Button>
  • 次のように、コードビハインドファイルMainWindow.xaml.csにコード行を追加します。

DataContext = ProxyCreator.MakeINotifyPropertyChanged<MainViewModel>();

  • 楽しい。

ここに画像の説明を入力してください

注意!!!すべての境界プロパティは、オーバーライドのために城プロキシによって使用されるため、キーワードvirtualで装飾する必要があります。


お使いのCastleのバージョンを知りたいです。私は3.3.0を使用していますが、CreateClassProxy方法は、それらのパラメータを持っていません:typeinterfaces to applyinterceptors
IAbstract

気にしないで、私は一般的なCreateClassProxy<T>方法を使用していました。非常に異なる...うーん、ジェネリックメソッドでなぜそんなに制限されているのだろうと思います。:(
IAbstract


5

ここを見てください:http : //dotnet-forum.de/blogs/thearchitect/archive/2012/11/01/die-optimale-implementierung-des-inotifypropertychanged-interfaces.aspx

ドイツ語で書かれていますが、ViewModelBase.csをダウンロードできます。cs-Fileのコメントはすべて英語で書かれています。

このViewModelBaseクラスを使用すると、よく知られている依存関係プロパティと同様のバインド可能なプロパティを実装できます。

public string SomeProperty
{
    get { return GetValue( () => SomeProperty ); }
    set { SetValue( () => SomeProperty, value ); }
}

1
リンクが壊れています。
Guge

4

Marcの回答を基にしたThomasの回答に基づいて、反射プロパティ変更コードを基本クラスに変更しました。

public abstract class PropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) 
            handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
    {
        if (selectorExpression == null)
            throw new ArgumentNullException("selectorExpression");
        var me = selectorExpression.Body as MemberExpression;

        // Nullable properties can be nested inside of a convert function
        if (me == null)
        {
            var ue = selectorExpression.Body as UnaryExpression;
            if (ue != null)
                me = ue.Operand as MemberExpression;
        }

        if (me == null)
            throw new ArgumentException("The body must be a member expression");

        OnPropertyChanged(me.Member.Name);
    }

    protected void SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression, params Expression<Func<object>>[] additonal)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return;
        field = value;
        OnPropertyChanged(selectorExpression);
        foreach (var item in additonal)
            OnPropertyChanged(item);
    }
}

使い方は、通知する追加のプロパティを渡すことができることを除いて、トーマスの回答と同じです。これは、グリッドで更新する必要がある計算列を処理するために必要でした。

private int _quantity;
private int _price;

public int Quantity 
{ 
    get { return _quantity; } 
    set { SetField(ref _quantity, value, () => Quantity, () => Total); } 
}
public int Price 
{ 
    get { return _price; } 
    set { SetField(ref _price, value, () => Price, () => Total); } 
}
public int Total { get { return _price * _quantity; } }

これにより、DataGridViewを介して公開されたBindingListに格納されているアイテムのコレクションが駆動されます。これにより、グリッドに対して手動でRefresh()を呼び出す必要がなくなりました。


4

やっぴという自分のアプローチを紹介しましょう。これはランタイムプロキシ|派生クラスジェネレーターに属し、Caste Projectのダイナミックプロキシのような既存のオブジェクトまたはタイプに新しい機能を追加します。

これにより、INotifyPropertyChangedを基本クラスに一度実装し、派生クラスを次のスタイルで宣言し、新しいプロパティのINotifyPropertyChangedを引き続きサポートできます。

public class Animal:Concept
{
    protected Animal(){}
    public virtual string Name { get; set; }
    public virtual int Age { get; set; }
}

派生クラスまたはプロキシ構築の複雑さは、次の行の背後に隠されている可能性があります。

var animal = Concept.Create<Animal>.New();

そして、すべてのINotifyPropertyChanged実装作業は次のように実行できます。

public class Concept:INotifyPropertyChanged
{
    //Hide constructor
    protected Concept(){}

    public static class Create<TConcept> where TConcept:Concept
    {
        //Construct derived Type calling PropertyProxy.ConstructType
        public static readonly Type Type = PropertyProxy.ConstructType<TConcept, Implementation<TConcept>>(new Type[0], true);
        //Create constructing delegate calling Constructor.Compile
        public static Func<TConcept> New = Constructor.Compile<Func<TConcept>>(Type);
    }


    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(PropertyChangedEventArgs eventArgs)
    {
        var caller = PropertyChanged;
        if(caller!=null)
        {
            caller(this, eventArgs);
        }
    }

    //define implementation
    public class Implementation<TConcept> : DefaultImplementation<TConcept> where TConcept:Concept
    {
        public override Func<TBaseType, TResult> OverrideGetter<TBaseType, TDeclaringType, TConstructedType, TResult>(PropertyInfo property)
        {
            return PropertyImplementation<TBaseType, TDeclaringType>.GetGetter<TResult>(property.Name);
        }
        /// <summary>
        /// Overriding property setter implementation.
        /// </summary>
        /// <typeparam name="TBaseType">Base type for implementation. TBaseType must be TConcept, and inherits all its constraints. Also TBaseType is TDeclaringType.</typeparam>
        /// <typeparam name="TDeclaringType">Type, declaring property.</typeparam>
        /// <typeparam name="TConstructedType">Constructed type. TConstructedType is TDeclaringType and TBaseType.</typeparam>
        /// <typeparam name="TResult">Type of property.</typeparam>
        /// <param name="property">PropertyInfo of property.</param>
        /// <returns>Delegate, corresponding to property setter implementation.</returns>
        public override Action<TBaseType, TResult> OverrideSetter<TBaseType, TDeclaringType, TConstructedType, TResult>(PropertyInfo property)
        {
            //This code called once for each declared property on derived type's initialization.
            //EventArgs instance is shared between all events for each concrete property.
            var eventArgs = new PropertyChangedEventArgs(property.Name);
            //get delegates for base calls.
            Action<TBaseType, TResult> setter = PropertyImplementation<TBaseType, TDeclaringType>.GetSetter<TResult>(property.Name);
            Func<TBaseType, TResult> getter = PropertyImplementation<TBaseType, TDeclaringType>.GetGetter<TResult>(property.Name);

            var comparer = EqualityComparer<TResult>.Default;

            return (pthis, value) =>
            {//This code executes each time property setter is called.
                if (comparer.Equals(value, getter(pthis))) return;
                //base. call
                setter(pthis, value);
                //Directly accessing Concept's protected method.
                pthis.OnPropertyChanged(eventArgs);
            };
        }
    }
}

それはリファクタリングに対して完全に安全であり、型構築後にリフレクションを使用せず、十分に高速です。


なぜTDeclaration型パラメーターが必要なのPropertyImplementationですか?確かにあなただけからゲッター/セッターを呼び出すのに適切なタイプを見つけることができます(callvirtではありません)TImplementation
Andrew Savinykh 2015年

TImplementationはほとんどの場合に機能します。例外は次のとおりです。1。「新しい」C#keyvordで再定義されたプロパティ。2.明示的なインターフェース実装のプロパティ。
Kelqualyn

3

これらの答えはすべてとてもいいです。

私の解決策は、コードスニペットを使用して仕事をすることです。

これは、PropertyChangedイベントへの最も単純な呼び出しを使用します。

このスニペットを保存し、「fullprop」スニペットと同じように使用します。

場所は、Visual Studioの[Tools \ Code Snippet Manager ...]メニューにあります。

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
            <Title>inotifypropfull</Title>
            <Shortcut>inotifypropfull</Shortcut>
            <HelpUrl>http://ofirzeitoun.wordpress.com/</HelpUrl>
            <Description>Code snippet for property and backing field with notification</Description>
            <Author>Ofir Zeitoun</Author>
            <SnippetTypes>
                <SnippetType>Expansion</SnippetType>
            </SnippetTypes>
        </Header>
        <Snippet>
            <Declarations>
                <Literal>
                    <ID>type</ID>
                    <ToolTip>Property type</ToolTip>
                    <Default>int</Default>
                </Literal>
                <Literal>
                    <ID>property</ID>
                    <ToolTip>Property name</ToolTip>
                    <Default>MyProperty</Default>
                </Literal>
                <Literal>
                    <ID>field</ID>
                    <ToolTip>The variable backing this property</ToolTip>
                    <Default>myVar</Default>
                </Literal>
            </Declarations>
            <Code Language="csharp">
                <![CDATA[private $type$ $field$;

    public $type$ $property$
    {
        get { return $field$;}
        set { 
            $field$ = value;
            var temp = PropertyChanged;
            if (temp != null)
            {
                temp(this, new PropertyChangedEventArgs("$property$"));
            }
        }
    }
    $end$]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>

通話は好きなように変更できます(上記のソリューションを使用するため)。


2

.NET 4.5でダイナミクスを使用している場合は、心配する必要はありませんINotifyPropertyChanged

dynamic obj = new ExpandoObject();
obj.Name = "John";

Nameがいくつかのコントロールにバインドされている場合、それは正常に機能します。


1
これを使用するデメリットはありますか?
juFo

2

別の組み合わせソリューションは、StackFrameを使用することです。

public class BaseViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void Set<T>(ref T field, T value)
    {
        MethodBase method = new StackFrame(1).GetMethod();
        field = value;
        Raise(method.Name.Substring(4));
    }

    protected void Raise(string propertyName)
    {
        var temp = PropertyChanged;
        if (temp != null)
        {
            temp(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

使用法:

public class TempVM : BaseViewModel
{
    private int _intP;
    public int IntP
    {
        get { return _intP; }
        set { Set<int>(ref _intP, value); }
    }
}

2
速いですか?スタックフレームへのアクセスは、いくつかの権限要件にバインドされていませんか?async / awaitを使用する状況では、それは堅牢ですか?
ステフェイン・グーリッホン

@StéphaneGourichonいいえ、そうではありません。スタックフレームにアクセスすると、ほとんどの場合、パフォーマンスが大幅に低下します。
ブルーノブラント2015

はい、あります、あなたはでそれを見ることができますcodereview.stackexchange.com/questions/13823/...
オフィール

インライン化するとget_Foo、リリースモードでメソッドが非表示になる場合があります。
bytecode77

2

再利用のために、ベースライブラリに拡張メソッドを作成しました。

public static class INotifyPropertyChangedExtensions
{
    public static bool SetPropertyAndNotify<T>(this INotifyPropertyChanged sender,
               PropertyChangedEventHandler handler, ref T field, T value, 
               [CallerMemberName] string propertyName = "",
               EqualityComparer<T> equalityComparer = null)
    {
        bool rtn = false;
        var eqComp = equalityComparer ?? EqualityComparer<T>.Default;
        if (!eqComp.Equals(field,value))
        {
            field = value;
            rtn = true;
            if (handler != null)
            {
                var args = new PropertyChangedEventArgs(propertyName);
                handler(sender, args);
            }
        }
        return rtn;
    }
}

CallerMemberNameAttributeのため、これは.Net 4.5で機能します。:あなたは以前の.NETバージョンでそれを使用したい場合は、からメソッドの宣言を変更する必要があり...,[CallerMemberName] string propertyName = "", ......,string propertyName, ...

使用法:

public class Dog : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    string _name;

    public string Name
    {
        get { return _name; }
        set
        {
            this.SetPropertyAndNotify(PropertyChanged, ref _name, value);
        }
    }
}

2

私はこの方法で解決しました(少し面倒ですが、実行時の方が確かに高速です)。

VBでは(申し訳ありませんが、C#での難しい翻訳ではないと思います)、REでこの置換を行います。

(?<Attr><(.*ComponentModel\.)Bindable\(True\)>)( |\r\n)*(?<Def>(Public|Private|Friend|Protected) .*Property )(?<Name>[^ ]*) As (?<Type>.*?)[ |\r\n](?![ |\r\n]*Get)

と:

Private _${Name} As ${Type}\r\n${Attr}\r\n${Def}${Name} As ${Type}\r\nGet\r\nReturn _${Name}\r\nEnd Get\r\nSet (Value As ${Type})\r\nIf _${Name} <> Value Then \r\n_${Name} = Value\r\nRaiseEvent PropertyChanged(Me, New ComponentModel.PropertyChangedEventArgs("${Name}"))\r\nEnd If\r\nEnd Set\r\nEnd Property\r\n

このトランスフォーマーは、次のようなすべてのコードです。

<Bindable(True)>
Protected Friend Property StartDate As DateTime?

Private _StartDate As DateTime?
<Bindable(True)>
Protected Friend Property StartDate As DateTime?
    Get
        Return _StartDate
    End Get
    Set(Value As DateTime?)
        If _StartDate <> Value Then
            _StartDate = Value
            RaiseEvent PropertyChange(Me, New ComponentModel.PropertyChangedEventArgs("StartDate"))
        End If
    End Set
End Property

そして、もっと読みやすいコードが必要な場合は、次の置換を行うだけで反対になります。

Private _(?<Name>.*) As (?<Type>.*)[\r\n ]*(?<Attr><(.*ComponentModel\.)Bindable\(True\)>)[\r\n ]*(?<Def>(Public|Private|Friend|Protected) .*Property )\k<Name> As \k<Type>[\r\n ]*Get[\r\n ]*Return _\k<Name>[\r\n ]*End Get[\r\n ]*Set\(Value As \k<Type>\)[\r\n ]*If _\k<Name> <> Value Then[\r\n ]*_\k<Name> = Value[\r\n ]*RaiseEvent PropertyChanged\(Me, New (.*ComponentModel\.)PropertyChangedEventArgs\("\k<Name>"\)\)[\r\n ]*End If[\r\n ]*End Set[\r\n ]*End Property

${Attr} ${Def} ${Name} As ${Type}

私はsetメソッドのILコードを置き換えるために投げますが、ILでコンパイルされたコードの多くを書くことはできません。


2

私はこれをスニペットとして保持します。C#6では、ハンドラーを呼び出すための構文がいくつか追加されています。

// INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

private void Set<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
{
    if (EqualityComparer<T>.Default.Equals(property, value) == false)
    {
        property = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

2

これは、NotifyPropertyChangedのUnity3Dまたは非CallerMemberNameバージョンです。

public abstract class Bindable : MonoBehaviour, INotifyPropertyChanged
{
    private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
    private static readonly StackTrace stackTrace = new StackTrace();
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    ///     Resolves a Property's name from a Lambda Expression passed in.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="property"></param>
    /// <returns></returns>
    internal string GetPropertyName<T>(Expression<Func<T>> property)
    {
        var expression = (MemberExpression) property.Body;
        var propertyName = expression.Member.Name;

        Debug.AssertFormat(propertyName != null, "Bindable Property shouldn't be null!");
        return propertyName;
    }

    #region Notification Handlers

    /// <summary>
    ///     Notify's all other objects listening that a value has changed for nominated propertyName
    /// </summary>
    /// <param name="propertyName"></param>
    internal void NotifyOfPropertyChange(string propertyName)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    /// <summary>
    ///     Notifies subscribers of the property change.
    /// </summary>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="property">The property expression.</param>
    internal void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property)
    {
        var propertyName = GetPropertyName(property);
        NotifyOfPropertyChange(propertyName);
    }

    /// <summary>
    ///     Raises the <see cref="PropertyChanged" /> event directly.
    /// </summary>
    /// <param name="e">The <see cref="PropertyChangedEventArgs" /> instance containing the event data.</param>
    internal void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    #endregion

    #region Getters

    /// <summary>
    ///     Gets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    internal T Get<T>(Expression<Func<T>> property)
    {
        var propertyName = GetPropertyName(property);
        return Get<T>(GetPropertyName(property));
    }

    /// <summary>
    ///     Gets the value of a property automatically based on its caller.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    internal T Get<T>()
    {
        var name = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name;
        return Get<T>(name);
    }

    /// <summary>
    ///     Gets the name of a property based on a string.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    internal T Get<T>(string name)
    {
        object value = null;
        if (_properties.TryGetValue(name, out value))
            return value == null ? default(T) : (T) value;
        return default(T);
    }

    #endregion

    #region Setters

    /// <summary>
    ///     Sets the value of a property whilst automatically looking up its caller name.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    internal void Set<T>(T value)
    {
        var propertyName = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name;
        Set(value, propertyName);
    }

    /// <summary>
    ///     Sets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="name"></param>
    internal void Set<T>(T value, string propertyName)
    {
        Debug.Assert(propertyName != null, "name != null");
        if (Equals(value, Get<T>(propertyName)))
            return;
        _properties[propertyName] = value;
        NotifyOfPropertyChange(propertyName);
    }

    /// <summary>
    ///     Sets the value of a property based off an Expression (()=>FieldName)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="property"></param>
    internal void Set<T>(T value, Expression<Func<T>> property)
    {
        var propertyName = GetPropertyName(property);

        Debug.Assert(propertyName != null, "name != null");

        if (Equals(value, Get<T>(propertyName)))
            return;
        _properties[propertyName] = value;
        NotifyOfPropertyChange(propertyName);
    }

    #endregion
}

このコードを使用すると、次のようなプロパティバッキングフィールドを記述できます。

  public string Text
    {
        get { return Get<string>(); }
        set { Set(value); }
    }

さらに、resharperでパターン/検索スニペットを作成すると、単純なプロップフィールドを上記のバッキングに変換することでワークフローを自動化することもできます。

検索パターン:

public $type$ $fname$ { get; set; }

パターンを置換:

public $type$ $fname$
{
    get { return Get<$type$>(); }
    set { Set(value); }
}

2

これに役立つ記事を書きました(https://msdn.microsoft.com/magazine/mt736453)。SolSoft.DataBinding NuGetパッケージを使用できます。次に、次のようなコードを記述できます。

public class TestViewModel : IRaisePropertyChanged
{
  public TestViewModel()
  {
    this.m_nameProperty = new NotifyProperty<string>(this, nameof(Name), null);
  }

  private readonly NotifyProperty<string> m_nameProperty;
  public string Name
  {
    get
    {
      return m_nameProperty.Value;
    }
    set
    {
      m_nameProperty.SetValue(value);
    }
  }

  // Plus implement IRaisePropertyChanged (or extend BaseViewModel)
}

利点:

  1. 基本クラスはオプションです
  2. すべての「設定値」に反映されない
  3. 他のプロパティに依存するプロパティを持つことができ、それらはすべて適切なイベントを自動的に発生させます(記事にはこの例があります)

2

これを行う方法は明らかにたくさんありますが、AOPマジックの回答を除いて、ローカルフィールドを参照せずにモデルのプロパティをビューモデルから直接設定することに関する回答はありません。

問題は、プロパティを参照できないことです。ただし、アクションを使用してそのプロパティを設定できます。

protected bool TrySetProperty<T>(Action<T> property, T newValue, T oldValue, [CallerMemberName] string propertyName = null)
{
    if (EqualityComparer<T>.Default.Equals(oldValue, newValue))
    {
        return false;
    }

    property(newValue);
    RaisePropertyChanged(propertyName);
    return true;
}

これは、次のコード抽出のように使用できます。

public int Prop {
    get => model.Prop;
    set => TrySetProperty(x => model.Prop = x, value, model.Prop);
}

このBitBucketリポジトリを調べて、メソッドの完全な実装と、LINQを使用するメソッドやリフレクションを使用するメソッドなど、同じ結果を実現するいくつかの異なる方法を確認してください。これらの方法はパフォーマンスが遅いことに注意してください。


1

これらの種類のプロパティを実装するときに考慮する必要があるかもしれない他の事柄は、両方ともINotifyPropertyChang * ed *がイベント引数クラスを使用するという事実です。

設定されているプロパティが多数ある場合、イベント引数クラスインスタンスの数は膨大になる可能性があります。これらは、文字列の爆発が発生する可能性がある領域の1つであるため、それらをキャッシュすることを検討してください。

この実装と、それが考え出された理由の説明を見てください。

ジョシュ・スミスのブログ


1

ActiveSharp-Automatic INotifyPropertyChangedを見つけたばかりですが、まだ使用していませんが、見た目は良いです。

それのウェブサイトから引用するには...


プロパティ名を文字列として指定せずにプロパティ変更通知を送信します。

代わりに、次のようなプロパティを記述します。

public int Foo
{
    get { return _foo; }
    set { SetValue(ref _foo, value); }  // <-- no property name here
}

プロパティの名前を文字列として含める必要がないことに注意してください。ActiveSharpはそれを確実かつ正確に計算します。これは、プロパティ実装が参照によってバッキングフィールド(_foo)を渡すという事実に基づいて機能します。(ActiveSharpはその「参照による」呼び出しを使用して、渡されたバッキングフィールドを識別し、フィールドからプロパティを識別します)。


1

リフレクションを使用するアイデア:

class ViewModelBase : INotifyPropertyChanged {

    public event PropertyChangedEventHandler PropertyChanged;

    bool Notify<T>(MethodBase mb, ref T oldValue, T newValue) {

        // Get Name of Property
        string name = mb.Name.Substring(4);

        // Detect Change
        bool changed = EqualityComparer<T>.Default.Equals(oldValue, newValue);

        // Return if no change
        if (!changed) return false;

        // Update value
        oldValue = newValue;

        // Raise Event
        if (PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }//if

        // Notify caller of change
        return true;

    }//method

    string name;

    public string Name {
        get { return name; }
        set {
            Notify(MethodInfo.GetCurrentMethod(), ref this.name, value);
        }
    }//method

}//class

これはすごくかっこいいです。表現アプローチよりも好きです。マイナス面は遅くなるはずです。
nawfal 14

1

この質問にはすでに膨大な数の回答が含まれていることに気づきましたが、どれも私にぴったりとは思えませんでした。私の問題は、パフォーマンスに影響を与えたくないので、その理由だけで少し冗長に耐えても構わないと思っています。また、自動プロパティもあまり気にしません。そのため、次の解決策が見つかりました。

public abstract class AbstractObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual bool SetValue<TKind>(ref TKind Source, TKind NewValue, params string[] Notify)
    {
        //Set value if the new value is different from the old
        if (!Source.Equals(NewValue))
        {
            Source = NewValue;

            //Notify all applicable properties
            foreach (var i in Notify)
                OnPropertyChanged(i);

            return true;
        }

        return false;
    }

    public AbstractObject()
    {
    }
}

言い換えれば、上記の解決策は、これを行うことを気にしない場合に便利です。

public class SomeObject : AbstractObject
{
    public string AnotherProperty
    {
        get
        {
            return someProperty ? "Car" : "Plane";
        }
    }

    bool someProperty = false;
    public bool SomeProperty
    {
        get
        {
            return someProperty;
        }
        set
        {
            SetValue(ref someProperty, value, "SomeProperty", "AnotherProperty");
        }
    }

    public SomeObject() : base()
    {
    }
}

長所

  • 反射なし
  • 古い値!=新しい値の場合にのみ通知
  • 一度に複数のプロパティに通知する

短所

  • 自動プロパティなし(ただし、両方のサポートを追加できます!)
  • ある程度冗長
  • ボクシング(小さなパフォーマンスヒット?)

悲しいかな、それはこれを行うよりも優れています、

set
{
    if (!someProperty.Equals(value))
    {
        someProperty = value;
        OnPropertyChanged("SomeProperty");
        OnPropertyChanged("AnotherProperty");
    }
}

追加の冗長性で悪夢となるすべてのプロパティについて、;-(

このソリューションは他のソリューションよりもパフォーマンスの点で優れているとは言いませんが、他のソリューションを好まない人にとっては実行可能なソリューションであることに注意してください。


1

私は、監視可能なパターンを実装するためにこの基本クラスを思いつきましたが、必要なことをほとんど実行します(セットと取得を「自動的に」実装)。私はプロトタイプとしてこれに1時間を費やしたので、単体テストはそれほど多くありませんが、コンセプトを証明しています。Dictionary<string, ObservablePropertyContext>プライベートフィールドの必要をなくすためにを使用していることに注意してください。

  public class ObservableByTracking<T> : IObservable<T>
  {
    private readonly Dictionary<string, ObservablePropertyContext> _expando;
    private bool _isDirty;

    public ObservableByTracking()
    {
      _expando = new Dictionary<string, ObservablePropertyContext>();

      var properties = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).ToList();
      foreach (var property in properties)
      {
        var valueContext = new ObservablePropertyContext(property.Name, property.PropertyType)
        {
          Value = GetDefault(property.PropertyType)
        };

        _expando[BuildKey(valueContext)] = valueContext;
      }
    }

    protected void SetValue<T>(Expression<Func<T>> expression, T value)
    {
      var keyContext = GetKeyContext(expression);
      var key = BuildKey(keyContext.PropertyName, keyContext.PropertyType);

      if (!_expando.ContainsKey(key))
      {
        throw new Exception($"Object doesn't contain {keyContext.PropertyName} property.");
      }

      var originalValue = (T)_expando[key].Value;
      if (EqualityComparer<T>.Default.Equals(originalValue, value))
      {
        return;
      }

      _expando[key].Value = value;
      _isDirty = true;
    }

    protected T GetValue<T>(Expression<Func<T>> expression)
    {
      var keyContext = GetKeyContext(expression);
      var key = BuildKey(keyContext.PropertyName, keyContext.PropertyType);

      if (!_expando.ContainsKey(key))
      {
        throw new Exception($"Object doesn't contain {keyContext.PropertyName} property.");
      }

      var value = _expando[key].Value;
      return (T)value;
    }

    private KeyContext GetKeyContext<T>(Expression<Func<T>> expression)
    {
      var castedExpression = expression.Body as MemberExpression;
      if (castedExpression == null)
      {
        throw new Exception($"Invalid expression.");
      }

      var parameterName = castedExpression.Member.Name;

      var propertyInfo = castedExpression.Member as PropertyInfo;
      if (propertyInfo == null)
      {
        throw new Exception($"Invalid expression.");
      }

      return new KeyContext {PropertyType = propertyInfo.PropertyType, PropertyName = parameterName};
    }

    private static string BuildKey(ObservablePropertyContext observablePropertyContext)
    {
      return $"{observablePropertyContext.Type.Name}.{observablePropertyContext.Name}";
    }

    private static string BuildKey(string parameterName, Type type)
    {
      return $"{type.Name}.{parameterName}";
    }

    private static object GetDefault(Type type)
    {
      if (type.IsValueType)
      {
        return Activator.CreateInstance(type);
      }
      return null;
    }

    public bool IsDirty()
    {
      return _isDirty;
    }

    public void SetPristine()
    {
      _isDirty = false;
    }

    private class KeyContext
    {
      public string PropertyName { get; set; }
      public Type PropertyType { get; set; }
    }
  }

  public interface IObservable<T>
  {
    bool IsDirty();
    void SetPristine();
  }

使い方はこちら

public class ObservableByTrackingTestClass : ObservableByTracking<ObservableByTrackingTestClass>
  {
    public ObservableByTrackingTestClass()
    {
      StringList = new List<string>();
      StringIList = new List<string>();
      NestedCollection = new List<ObservableByTrackingTestClass>();
    }

    public IEnumerable<string> StringList
    {
      get { return GetValue(() => StringList); }
      set { SetValue(() => StringIList, value); }
    }

    public IList<string> StringIList
    {
      get { return GetValue(() => StringIList); }
      set { SetValue(() => StringIList, value); }
    }

    public int IntProperty
    {
      get { return GetValue(() => IntProperty); }
      set { SetValue(() => IntProperty, value); }
    }

    public ObservableByTrackingTestClass NestedChild
    {
      get { return GetValue(() => NestedChild); }
      set { SetValue(() => NestedChild, value); }
    }

    public IList<ObservableByTrackingTestClass> NestedCollection
    {
      get { return GetValue(() => NestedCollection); }
      set { SetValue(() => NestedCollection, value); }
    }

    public string StringProperty
    {
      get { return GetValue(() => StringProperty); }
      set { SetValue(() => StringProperty, value); }
    }
  }

1

ReactivePropertyを使用することをお勧めします。これは、Fodyを除いて最短の方法です。

public class Data : INotifyPropertyChanged
{
    // boiler-plate
    ...
    // props
    private string name;
    public string Name
    {
        get { return name; }
        set { SetField(ref name, value, "Name"); }
    }
}

代わりに

public class Data
{
    // Don't need boiler-plate and INotifyPropertyChanged

    // props
    public ReactiveProperty<string> Name { get; } = new ReactiveProperty<string>();
}

ドキュメント


0

別のアイデア...

 public class ViewModelBase : INotifyPropertyChanged
{
    private Dictionary<string, object> _propertyStore = new Dictionary<string, object>();
    protected virtual void SetValue<T>(T value, [CallerMemberName] string propertyName="") {
        _propertyStore[propertyName] = value;
        OnPropertyChanged(propertyName);
    }
    protected virtual T GetValue<T>([CallerMemberName] string propertyName = "")
    {
        object ret;
        if (_propertyStore.TryGetValue(propertyName, out ret))
        {
            return (T)ret;
        }
        else
        {
            return default(T);
        }
    }

    //Usage
    //public string SomeProperty {
    //    get { return GetValue<string>();  }
    //    set { SetValue(value); }
    //}

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        var temp = PropertyChanged;
        if (temp != null)
            temp.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

0

=> ここに次の機能を持つ私のソリューション

 public ResourceStatus Status
 {
     get { return _status; }
     set
     {
         _status = value;
         Notify(Npcea.Status,Npcea.Comments);
     }
 }
  1. 反省なし
  2. 短い表記
  3. ビジネスコードに魔法の文字列はありません
  4. アプリケーション全体でのPropertyChangedEventArgsの再利用性
  5. 1つのステートメントで複数のプロパティを通知する可能性

0

これを使って

using System;
using System.ComponentModel;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;


public static class ObservableFactory
{
    public static T Create<T>(T target)
    {
        if (!typeof(T).IsInterface)
            throw new ArgumentException("Target should be an interface", "target");

        var proxy = new Observable<T>(target);
        return (T)proxy.GetTransparentProxy();
    }
}

internal class Observable<T> : RealProxy, INotifyPropertyChanged, INotifyPropertyChanging
{
    private readonly T target;

    internal Observable(T target)
        : base(ImplementINotify(typeof(T)))
    {
        this.target = target;
    }

    public override IMessage Invoke(IMessage msg)
    {
        var methodCall = msg as IMethodCallMessage;

        if (methodCall != null)
        {
            return HandleMethodCall(methodCall);
        }

        return null;
    }

    public event PropertyChangingEventHandler PropertyChanging;
    public event PropertyChangedEventHandler PropertyChanged;



    IMessage HandleMethodCall(IMethodCallMessage methodCall)
    {
        var isPropertySetterCall = methodCall.MethodName.StartsWith("set_");
        var propertyName = isPropertySetterCall ? methodCall.MethodName.Substring(4) : null;

        if (isPropertySetterCall)
        {
            OnPropertyChanging(propertyName);
        }

        try
        {
            object methodCalltarget = target;

            if (methodCall.MethodName == "add_PropertyChanged" || methodCall.MethodName == "remove_PropertyChanged"||
                methodCall.MethodName == "add_PropertyChanging" || methodCall.MethodName == "remove_PropertyChanging")
            {
                methodCalltarget = this;
            }

            var result = methodCall.MethodBase.Invoke(methodCalltarget, methodCall.InArgs);

            if (isPropertySetterCall)
            {
                OnPropertyChanged(methodCall.MethodName.Substring(4));
            }

            return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
        }
        catch (TargetInvocationException invocationException)
        {
            var exception = invocationException.InnerException;
            return new ReturnMessage(exception, methodCall);
        }
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanging(string propertyName)
    {
        var handler = PropertyChanging;
        if (handler != null) handler(this, new PropertyChangingEventArgs(propertyName));
    }

    public static Type ImplementINotify(Type objectType)
    {
        var tempAssemblyName = new AssemblyName(Guid.NewGuid().ToString());

        var dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
            tempAssemblyName, AssemblyBuilderAccess.RunAndCollect);

        var moduleBuilder = dynamicAssembly.DefineDynamicModule(
            tempAssemblyName.Name,
            tempAssemblyName + ".dll");

        var typeBuilder = moduleBuilder.DefineType(
            objectType.FullName, TypeAttributes.Public | TypeAttributes.Interface | TypeAttributes.Abstract);

        typeBuilder.AddInterfaceImplementation(objectType);
        typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanged));
        typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanging));
        var newType = typeBuilder.CreateType();
        return newType;
    }
}

}

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.