0

私が取り組んでいるプロジェクトの 1 つで遭遇した SelectMany の使用について、いくつか質問があります。以下は、その使用法を再現した小さなサンプルです (さまざまな時点での状態を確認するために使用していたいくつかの Console.WriteLines を使用しています)。

public partial class MainWindow : INotifyPropertyChanged
{
    private bool _cb1, _cb2, _cb3, _isDirty;
    private readonly ISubject<Unit> _cb1HasChanged = new Subject<Unit>();
    private readonly ISubject<Unit> _cb2HasChanged = new Subject<Unit>();
    private readonly ISubject<Unit> _cb3HasChanged = new Subject<Unit>();
    private readonly ISubject<string> _initialState = new ReplaySubject<string>(1);

    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
        ObserveCheckBoxes();

        var initialState = string.Format("{0}{1}{2}", CB1, CB2, CB3);
        _initialState.OnNext(initialState);
        Console.WriteLine("INITIAL STATE: " + initialState);
    }

    public bool CB1
    {
        get
        {
            return _cb1;
        }
        set
        {
            _cb1 = value;
            _cb1HasChanged.OnNext(Unit.Default);
        }
    }

    public bool CB2
    {
        get
        {
            return _cb2;
        }
        set
        {
            _cb2 = value;
            _cb2HasChanged.OnNext(Unit.Default);
        }
    }

    public bool CB3
    {
        get
        {
            return _cb3;
        }
        set
        {
            _cb3 = value;
            _cb3HasChanged.OnNext(Unit.Default);
        }
    }

    public bool IsDirty
    {
        get
        {
            return _isDirty;
        }
        set
        {
            _isDirty = value;
            OnPropertyChanged("IsDirty");
        }
    }

    private void ObserveCheckBoxes()
    {
        var checkBoxChanges = new[]
            {
                _cb1HasChanged,
                _cb2HasChanged,
                _cb3HasChanged
            }
            .Merge();

        var isDirty = _initialState.SelectMany(initialState => checkBoxChanges
                                                                   .Select(_ => GetNewState(initialState))
                                                                   .Select(updatedState => initialState != updatedState)
                                                                   .StartWith(false)
                                                                   .TakeUntil(_initialState.Skip(1)));
        isDirty.Subscribe(d => IsDirty = d);
    }

    private string GetNewState(string initialState = null)
    {
        string update = string.Format("{0}{1}{2}", CB1, CB2, CB3);
        if (initialState != null)
        {
            Console.WriteLine("CREATING UPDATE: " + update + " INITIAL STATE: " + initialState);
        }
        else
        {
            Console.WriteLine("CREATING UPDATE: " + update);
        }
        return update;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    void OnPropertyChanged(string prop)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }
    }

    private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
    {
        var newState = GetNewState();
        _initialState.OnNext(newState);
        Console.WriteLine("SAVED AS: " + newState);
    }
}

そしてxaml:

<Window x:Class="WpfSB2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <StackPanel>
            <CheckBox IsChecked="{Binding CB1}"></CheckBox>
            <CheckBox IsChecked="{Binding CB2}"></CheckBox>
            <CheckBox IsChecked="{Binding CB3}"></CheckBox>
            <Button IsEnabled="{Binding IsDirty}" Click="Button_Click">APPLY</Button>
        </StackPanel>
    </Grid>
</Window>

この小さなアプリが行うことは、3 つのチェックボックス (最初はすべてオフ) と [適用] ボタンを表示することです。チェックボックスの状態が変わると、ボタンが有効になり、クリックすると、チェックボックスの状態が再び変わるまで無効になります。チェックボックスの状態を変更してから初期状態に戻すと、ボタンは適切に有効/無効になります。アプリは期待どおりに動作します。理由/方法を理解しようとしています。

今質問:

  • _initialState またはチェック ボックスが変更されるたびに、SelectMany 呼び出しがトリガーされますか?

  • _initialState.OnNext(initialState); の最初の呼び出し。(コンストラクターで) SelectMany コードに関しては、実際には何もしません。SelectMany コードに到達していることがわかりますが、実際には何も行われていません (つまり、checkBoxChanges.Select セクションにブレークポイントを設定すると、ブレークしますが、実際には何も選択されません)。これは、まだどのチェックボックスにも変更が加えられていないためですか?

  • 予想どおり、checkBox をチェックすると isDirty チェックがトリガーされます。単一のチェックボックスを初めて変更したときに、この SelectMany ステートメントで何が起こっているのでしょうか?

  • チェックボックスをオンにすると、[適用] ボタンが有効になり、[適用] をクリックします。これにより、_initialState.OnNext(newState); が発生します。呼ばれること。私の最初の質問と同様に、SelectMany ステートメントでは何も起こらないようです。初期状態で新しい値を取得すると、何かが再計算されると思いましたが、 isDirty.Subscribe(d => IsDirty = d); の OnNext ハンドラーに直接行くようです。

  • Apply を押したところ、_initialState.OnNext が合計 2 回呼び出されました。新しいチェックボックスをオンにした場合、SelectMany はそれをどのように処理しますか? _initialState の過去のすべての状態を通過しますか? これらの値は、オブザーバブルが破棄されるまで保存されますか?

  • StartsWith/TakeUntil/Skip 行は何をしているのですか? TakeUntil 行を削除すると、SelectMany 句が _initialState の過去のすべての値を調べ始め、比較する実際の現在の状態がどれであるかについて混乱するため、アプリが正しく動作しなくなることに気付きました。

追加情報が必要な場合はお知らせください。

4

1 に答える 1

2

あなたの問題の重要な部分は、SelectMany を理解していることだと思います。SelectMany は「1 つから、複数を選択する」と表現するとわかりやすいと思います。

ソース シーケンスの値ごとに、SelectMany は別のシーケンスから 0、1、または複数の値を提供します。

あなたの場合、ソースシーケンスは_initialState. そのシーケンスから値が生成されるたびに、提供された「内部シーケンス」にサブスクライブします。

質問に直接答えるには:
1)_initialStateが値をプッシュすると、その値が SelectMany オペレーターに渡され、提供された「内部シーケンス」にサブスクライブします。

2) 最初の呼び出しは、InitialState を ReplaySubject のバッファーに入れています。これは、最初に_initialStateシーケンスをサブスクライブすると、すぐに値がプッシュされることを意味します。GetNewState にブレーク ポイントを配置すると、これが機能していることがわかります。

3) チェック ボックスをオンにすると、セッターが呼び出され、OnNext _cbXHasChanged サブジェクト (yuck) が Merged シーケンス (checkBoxChanges) に流れ、次に SelectMany デリゲート クエリに流れます。

4) チェックボックスが新しい値をプッシュするまで何も起こりません (それらはリプレイサブジェクトではありません)。

5-6) はい、2 回呼び出したので selectMany デリゲートを 2 回実行しますが、2 番目の「内部シーケンス」が開始されると、TakeUntil は最初の「内部シーケンス」を終了します。

これについては、(私のサイト) IntroToRx.comの SelectMany の章で詳しく説明しています。

于 2013-05-28T11:17:24.610 に答える