LoginSignup
4
4

More than 5 years have passed since last update.

Rx の SelectMany について勘違いしていた

Posted at

Rxは便利すぎるのですが、まだ不慣れな私です。
ReactivePropertyを用いてMVVMパターンを組み立てていたところ、以下のようなシーンに遭遇しました。
以下、単純化したサンプルです。

example.cs
using System;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using Prism.Mvvm;
using Reactive.Bindings;
using Reactive.Bindings.Extensions;

namespace ConsoleExample
{
    class Model1 : BindableBase
    {
        private int _number = 0;
        public int Number
        {
            get { return _number; }
            set { SetProperty(ref _number, value); }
        }
    }

    class Model2 : BindableBase
    {
        private Model1 _model1 = null;
        public Model1 Model1
        {
            get { return _model1; }
            set { SetProperty(ref _model1, value); }
        }
    }

    class ViewModel
    {
        public Model2 Model2 { get; }
        public ReactiveProperty<int> Number { get; }

        public ViewModel()
        {
            Model2 = new Model2();
            Number = Model2
                .ToReactivePropertyAsSynchronized(m2 => m2.Model1)
                .Where(m1 => m1 != null)
                // ここのSelectMany
                .SelectMany(m1 => m1.ToReactivePropertyAsSynchronized(x => x.Number))
                .ToReactiveProperty(0);
        }
    }

    class View
    {
        private ViewModel _viewModel;

        public View(ViewModel viewModel)
        {
            _viewModel = viewModel;
            _viewModel.Number.Subscribe(x => Console.WriteLine(x));
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var viewModel = new ViewModel();
            var view = new View(viewModel);

            var model1_1 = new Model1();
            var model1_2 = new Model1();
            Observable.Timer(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(1)).Subscribe(x => model1_1.Number = (int)x);
            Observable.Timer(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(1)).Subscribe(x => model1_2.Number = (int)(x * 10));
            viewModel.Model2.Model1 = model1_1; // 1, 2, 3...
            Thread.Sleep(5500); // 5.5秒待つ
            viewModel.Model2.Model1 = model1_2; // 10, 20, 30...

            Console.ReadLine();
        }
    }
}

出力は以下のようになります。

0
1
2
3
4
5
50
60
6
70
7
80
8
90
9
100
10

このように、model1_1とmodel1_2の内容が混ざり合っています。
私は当初、以下のようにmodel1_1からmodel1_2へ切り替わると思っていました。

0
1
2
3
4
5
50
60
70
80
90
100

しかし、そうはなっていません。
SelectManyは、以下の処理と同じだったのです。

example.cs
Select(m1 => m1.ToReactivePropertyAsSynchronized(x => x.Number)).Merge()
// ↑SelectMany(m1 => m1.ToReactivePropertyAsSynchronized(x => x.Number))と同じ結果

model1_1とmodel1_2が混ざり合わずに切り替わるようにするためには、以下のようにします。

example.cs
Select(m1 => m1.ToReactivePropertyAsSynchronized(x => x.Number)).Switch()
4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4