Skip to content

SelectMany

1. 概要

SelectMany は、上流から流れてくる各値に対して Observable を生成し、それらすべてを フラットに合流 させて下流へ流すオペレーターです。

他言語では flatMapmergeMap と呼ばれる操作に相当します。上流の 1 つの値から複数の値を展開したい場合や、値をもとに別の Observable を購読してその結果を合流させたい場合に使います。

2. シグネチャ

値から Observable を生成して合流する

csharp
public static Observable<TResult> SelectMany<TSource, TResult>(
    this Observable<TSource> source,
    Func<TSource, Observable<TResult>> selector)

各値に selector を適用して Observable を生成し、すべての内部 Observable の値をフラットに合流させます。

csharp
source.SelectMany(x => Observable.Range(0, x))

値から Observable を生成し、結果を変換して合流する

csharp
public static Observable<TResult> SelectMany<TSource, TCollection, TResult>(
    this Observable<TSource> source,
    Func<TSource, Observable<TCollection>> collectionSelector,
    Func<TSource, TCollection, TResult> resultSelector)

collectionSelector で内部 Observable を生成し、各内部値と元の上流値を resultSelector で組み合わせて変換します。

csharp
source.SelectMany(
    x => Observable.Range(0, x),
    (original, inner) => $"{original} -> {inner}")

インデックス付きで Observable を生成して合流する

csharp
public static Observable<TResult> SelectMany<TSource, TResult>(
    this Observable<TSource> source,
    Func<TSource, int, Observable<TResult>> selector)

selector の第 2 引数に 0 始まりのインデックスが渡されます。

csharp
source.SelectMany((x, i) => Observable.Return($"[{i}] {x}"))

インデックス付きで Observable を生成し、結果を変換して合流する

csharp
public static Observable<TResult> SelectMany<TSource, TCollection, TResult>(
    this Observable<TSource> source,
    Func<TSource, int, Observable<TCollection>> collectionSelector,
    Func<TSource, int, TCollection, int, TResult> resultSelector)

collectionSelectorresultSelector の両方にインデックスが渡される overload です。resultSelector には、上流のインデックスと内部 Observable のインデックスの両方が渡されます。

csharp
source.SelectMany(
    (x, si) => Observable.Range(0, x),
    (src, si, col, ci) => $"src[{si}]={src}, col[{ci}]={col}")

overload の使い分け

overload使う場面
SelectMany(Func<TSource, Observable<TResult>>)各値から Observable を展開して合流する基本形
SelectMany(Func<TSource, Observable<TCollection>>, Func<..., TResult>)元の値と内部値を組み合わせた変換が必要な場合
SelectMany(Func<TSource, int, Observable<TResult>>)インデックスを利用したい場合
SelectMany(Func<..., int, ...>, Func<..., int, ..., int, TResult>)インデックスと結果変換の両方が必要な場合

3. マーブルダイアグラム

SelectMany のマーブルダイアグラム

上流の各値から内部 Observable が生成され、それらの値がフラットに合流して下流へ流れます。内部 Observable が並行して動作するため、出力順は各内部 Observable の発行タイミングに依存します。

4. サンプルコード

csharp
using R3;

var source = Observable.Create<int>(observer =>
{
    observer.OnNext(1);
    observer.OnNext(2);
    observer.OnNext(3);
    observer.OnCompleted();
    return Disposable.Empty;
});

// 各値から Range を展開して合流
source.SelectMany(x => Observable.Range(1, x))
    .Subscribe(x => Console.WriteLine(x));
// 出力: 1, 1, 2, 1, 2, 3

元の値と内部値を組み合わせる例:

csharp
using R3;

var source = Observable.Range(1, 3);

source.SelectMany(
        x => Observable.Range(1, x),
        (original, inner) => $"{original} * {inner} = {original * inner}")
    .Subscribe(x => Console.WriteLine(x));
// 出力:
// 1 * 1 = 1
// 2 * 1 = 2
// 2 * 2 = 4
// 3 * 1 = 3
// 3 * 2 = 6
// 3 * 3 = 9

5. 補足

  • 単純な 1 対 1 の同期変換には Select を使用してください。SelectMany は 1 対多の展開やネストされた Observable のフラット化に使います。
  • 非同期処理で 1 対 1 の変換を行いたい場合は SelectAwait のほうが適切です。