web-dev-qa-db-ja.com

Flatten IEnumerable <IEnumerable <>>;ジェネリックを理解する

私はこの拡張メソッド(コンパイルする)を書きました:

public static IEnumerable<J> Flatten<T, J>(this IEnumerable<T> @this) 
                                           where T : IEnumerable<J>
{
    foreach (T t in @this)
        foreach (J j in t)
            yield return j;
}

以下のコードは、コンパイル時エラー(適切なメソッドが見つからない)を引き起こします、理由

IEnumerable<IEnumerable<int>> foo = new int[2][];
var bar = foo.Flatten();

以下のように拡張機能を実装すると、コンパイル時エラーは発生しません。

public static IEnumerable<J> Flatten<J>(this IEnumerable<IEnumerable<J>> @this)
{
    foreach (IEnumerable<J> js in @this)
        foreach (J j in js)
            yield return j;
}

Edit(2):この質問は答えられたと思いますが、過負荷の解決と型の制約に関して別の質問がありました。私がここに置いたこの質問: タイプ制約がメソッドシグネチャの一部ではないのはなぜですか?

36
Daryl

まず、Flatten()は必要ありません。そのメソッドはすでに存在し、SelectMany()と呼ばれます。次のように使用できます。

_IEnumerable<IEnumerable<int>> foo = new [] { new[] {1, 2}, new[] {3, 4} };
var bar = foo.SelectMany(x => x); // bar is {1, 2, 3, 4}
_

第二に、ジェネリック型の推論はメソッドへの引数に基づいてのみ機能し、メソッドに関連付けられたジェネリック制約ではないため、最初の試みは機能しません。 J汎用パラメーターを直接使用する引数がないため、型推論エンジンはJがどうあるべきかを推測できず、したがって、メソッドが候補であるとは見なしません。

SelectMany()がこれをどのように回避するかを見るのは啓発的です:追加の_Func<TSource, TResult>_引数が必要です。これにより、型推論エンジンは両方の汎用型を判別できます。これらは両方とも、メソッドに提供された引数のみに基づいて使用できるためです。

82
dlev

dlevの答えは結構です。もう少し情報を追加すると思いました。

具体的には、ジェネリックを使用してIEnumerable<T>に一種の共分散を実装しようとしていることに注意してください。 C#4以降では、IEnumerable<T>はすでに共変です。

2番目の例はこれを示しています。あなたが持っている場合

List<List<int>> lists = whatever;
foreach(int x in lists.Flatten()) { ... }

次に、型推論により、List<List<int>>IE<List<int>>に変換可能であり、List<int>IE<int>に変換可能であり、したがって、共分散のため、IE<List<int>>IE<IE<int>>に変換可能であると推論されます。それは型推論に何かを続けることを与えます。 Tはintであり、すべてが良好であると推測できます。

これはC#3では機能しません。共分散のない世界では、生活は少し難しくなりますが、Cast<T>拡張メソッドを慎重に使用することで問題を解決できます。

17
Eric Lippert