web-dev-qa-db-ja.com

ネストされた(子)オブジェクトのINotifyPropertyChangedをサブスクライブします。

ネストされた(子)オブジェクトのINotifyPropertyChangedイベントを処理するためのクリーンでelegantソリューションを探しています。コード例:

public class Person : INotifyPropertyChanged {

  private string _firstName;
  private int _age;
  private Person _bestFriend;

  public string FirstName {
    get { return _firstName; }
    set {
      // Short implementation for simplicity reasons
      _firstName = value;
      RaisePropertyChanged("FirstName");
    }
  }

  public int Age {
    get { return _age; }
    set {
      // Short implementation for simplicity reasons
      _age = value;
      RaisePropertyChanged("Age");
    }
  }

  public Person BestFriend {
    get { return _bestFriend; }
    set {
      // - Unsubscribe from _bestFriend's INotifyPropertyChanged Event
      //   if not null

      _bestFriend = value;
      RaisePropertyChanged("BestFriend");

      // - Subscribe to _bestFriend's INotifyPropertyChanged Event if not null
      // - When _bestFriend's INotifyPropertyChanged Event is fired, i'd like
      //   to have the RaisePropertyChanged("BestFriend") method invoked
      // - Also, I guess some kind of *weak* event handler is required
      //   if a Person instance i beeing destroyed
    }
  }

  // **INotifyPropertyChanged implementation**
  // Implementation of RaisePropertyChanged method

}

BestFriendプロパティとその値の設定に注目してください。今私はこれを手動で行うことができることを知っています、コメントに記載されているすべての手順を実装します。しかし、これは多くのコードになるでしょう。特に、このようにINotifyPropertyChangedを実装する多くの子プロパティを計画している場合はなおさらです。もちろん、それらは常に同じタイプになるとは限りません。それらが共通しているのは、INotifyPropertyChangedインターフェースだけです。

その理由は、私の実際のシナリオでは、いくつかのレイヤーにネストされたオブジェクトプロパティを持つ複雑な「アイテム」(カート内)オブジェクトがあり(アイテムには「ライセンス」オブジェクトがあり、それ自体が子オブジェクトを持つことができる)、私は価格を再計算できるようにするには、「アイテム」の単一の変更について通知を受ける必要があります。

これを解決するのに役立つヒントや実装はありますか?

残念ながら、目標を達成するためにPostSharpのようなビルド後の手順を使用することはできません。

32
thmshd

すぐに使えるソリューションを見つけることができなかったので、Pieters(およびMarks)の提案(ありがとう!)に基づいてカスタム実装を行いました。

クラスを使用すると、深いオブジェクトツリーの変更が通知されます。これは、INotifyPropertyChanged実装タイプとINotifyCollectionChanged *実装コレクションで機能します(明らかに、私はObservableCollection)。

これが非常にクリーンでエレガントなソリューションであることがわかったと思いますが、完全にはテストされていませんが、拡張の余地があります。使い方はかなり簡単です。静的なChangeListenerメソッドを使用してCreateのインスタンスを作成し、INotifyPropertyChangedを渡すだけです。

var listener = ChangeListener.Create(myViewModel);
listener.PropertyChanged += 
    new PropertyChangedEventHandler(listener_PropertyChanged);

PropertyChangedEventArgsPropertyNameを提供します。これは常にオブジェクトの完全な「パス」になります。たとえば、個人の「BestFriend」名を変更すると、PropertyNameは「BestFriend.Name」になります。BestFriendに子のコレクションがあり、年齢を変更すると、値は「BestFriend.Children []。Age」などになります。オブジェクトが破棄されるときはDisposeを忘れないでください。そうすれば、(できれば)すべてのイベントリスナーから完全にサブスクライブ解除されます。

.NET(4でテスト済み)およびSilverlight(4でテスト済み)でコンパイルされます。コードは3つのクラスに分かれているので、コードをGist 70545に投稿しました。 https://Gist.github.com/70545 **

*)コードが機能している1つの理由は、ObservableCollectionINotifyPropertyChangedを実装していることです。それ以外の場合、期待どおりに機能しません。これは既知の警告です

**) MITライセンス の下でリリースされた無料の使用

21
thmshd

私が探しているのは、WPFバインディングのようなものだと思います。

INotifyPropertyChangedの仕組みは、プロパティBestFriendが変更されたときにRaisePropertyChanged("BestFriend");onlyである必要があることです。オブジェクト自体に何かが変更されたときではありません。

これを実装する方法は、2ステップのINotifyPropertyChangedイベントハンドラーです。リスナーは、Personの変更されたイベントに登録します。 BestFriendが設定/変更されたら、BestFriendPersonの変更されたイベントに登録します。次に、そのオブジェクトの変更されたイベントのリスニングを開始します。

これがまさに、WPFバインディングがこれを実装する方法です。ネストされたオブジェクトの変更のリスニングは、そのシステムを介して行われます。

これをPersonに実装するときに機能しない理由は、レベルが非常に深くなる可能性があり、BestFriendの変更されたイベントが何も意味しないためです(「何が変更されたのか?」 )。この問題は、循環関係がある場合に大きくなります。あなたの月の親友はあなたの親友の母親です。次に、プロパティの1つが変更されると、スタックオーバーフローが発生します。

したがって、これを解決するには、リスナーを作成できるクラスを作成します。たとえば、BestFriend.FirstNameでリスナーを構築します。次に、そのクラスは、Personの変更されたイベントにイベントハンドラーを配置し、BestFriendの変更をリッスンします。次に、それが変更されると、BestFriendにリスナーを配置し、FirstNameの変更をリッスンします。次に、それが変更されると、イベントを発生させ、それを聞くことができます。これが基本的にWPFバインディングの動作方法です。

WPFバインディングの詳細については、 http://msdn.Microsoft.com/en-us/library/ms750413.aspx を参照してください。

16

興味深い解決策トーマス。

別の解決策を見つけました。それはプロパゲーターデザインパターンと呼ばれています。詳細については、Webを参照してください(たとえば、CodeProject: Propagator in C#-An Alternative to the Observer Design Pattern )。

基本的に、これは依存関係ネットワーク内のオブジェクトを更新するためのパターンです。オブジェクトのネットワークを通じて状態の変更をプッシュする必要がある場合に非常に役立ちます。状態の変化は、伝播関数のネットワークを通過するオブジェクト自体によって表されます。状態変化をオブジェクトとしてカプセル化することにより、プロパゲーターは疎結合になります。

再利用可能なPropagatorクラスのクラス図:

A class diagram of the re-usable Propagator classes

詳しくは CodeProject をご覧ください。

3
Marek Takac

CodeProjectで私のソリューションをチェックアウトしてください: http://www.codeproject.com/Articles/775831/INotifyPropertyChanged-propagator それはまさにあなたが必要とすることを行います-このビューモデルまたはネストされたビューモデルの関連する依存関係が変更されたときに依存プロパティを(エレガントな方法で)伝達するのに役立ちます。

public decimal ExchTotalPrice
{
    get
    {
        RaiseMeWhen(this, has => has.Changed(_ => _.TotalPrice));
        RaiseMeWhen(ExchangeRate, has => has.Changed(_ => _.Rate));
        return TotalPrice * ExchangeRate.Rate;
    }
}
0
KolA

これを行うための簡単なヘルパーを作成しました。親ビューモデルでBubblePropertyChanged(x => x.BestFriend)を呼び出すだけです。 n.b.親にNotifyPropertyChagnedというメソッドがあることを前提としていますが、それを適応させることができます。

        /// <summary>
    /// Bubbles up property changed events from a child viewmodel that implements {INotifyPropertyChanged} to the parent keeping
    /// the naming hierarchy in place.
    /// This is useful for nested view models. 
    /// </summary>
    /// <param name="property">Child property that is a viewmodel implementing INotifyPropertyChanged.</param>
    /// <returns></returns>
    public IDisposable BubblePropertyChanged(Expression<Func<INotifyPropertyChanged>> property)
    {
        // This step is relatively expensive but only called once during setup.
        MemberExpression body = (MemberExpression)property.Body;
        var prefix = body.Member.Name + ".";

        INotifyPropertyChanged child = property.Compile().Invoke();

        PropertyChangedEventHandler handler = (sender, e) =>
        {
            this.NotifyPropertyChanged(prefix + e.PropertyName);
        };

        child.PropertyChanged += handler;

        return Disposable.Create(() => { child.PropertyChanged -= handler; });
    }
0
DanH

私は1日間Webを検索していて、Sacha Barberから別の素晴らしい解決策を見つけました。

http://www.codeproject.com/Articles/166530/A-Chained-Property-Observer

彼は連鎖プロパティオブザーバー内に弱い参照を作成しました。この機能を実装する別の優れた方法を見たい場合は、記事をご覧ください。

また、Reactive Extensions @ http://www.rowanbeach.com/rowan-beach-blog/a-system-reactive-property-change-observer/ を使用したすてきな実装についても触れておきます。

このソリューションは、オブザーバーの完全なチェーンではなく、1レベルのオブザーバーでのみ機能します。

0
Pascalsz