web-dev-qa-db-ja.com

IEnumerable <T>ではなくIList <T>に.ForEach()があるのはなぜですか?

可能性のある複製:
IEnumerableインターフェイスにForEach拡張メソッドがないのはなぜですか?

LINQ-yコードを作成するときに、.ForEach()が使いやすいイディオムであることに気付きました。たとえば、次のコードは次の入力を受け取り、これらの出力を生成します。

_{ "One" } => "One"
{ "One", "Two" } => "One, Two"
{ "One", "Two", "Three", "Four" } => "One, Two, Three and Four";
_

そしてコード:

_private string InsertCommasAttempt(IEnumerable<string> words)
{
    List<string> wordList = words.ToList();
    StringBuilder sb = new StringBuilder();
    var wordsAndSeparators = wordList.Select((string Word, int pos) =>
        {
            if (pos == 0) return new { Word = Word, Leading = string.Empty };
            if (pos == wordList.Count - 1) return new { Word = Word, Leading = " and " };
            return new { Word = Word, Leading = ", " };
        });

    wordsAndSeparators.ToList().ForEach(v => sb.Append(v.Leading).Append(v.Word));
    return sb.ToString();
}
_

最後から2行目の.ToList()の前に挿入された.ForEach()に注意してください。

_IEnumerable<T>_の拡張メソッドとして.ForEach()を使用できないのはなぜですか?このような例では、奇妙に思えます。

52
Olema

ForEach(Action)IEnumerable<T>が存在する前に存在したためです。

他の拡張メソッドでは追加されなかったため、C#の設計者はそれが悪いデザインだと感じ、foreach構成を好むと考えられます。


編集:

独自の拡張メソッドを作成したい場合は、List<T>の拡張メソッドをオーバーライドすることはありませんが、IEnumerable<T>を実装する他のクラスでは機能します。

public static class IEnumerableExtensions
{
  public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
  {
    foreach (T item in source)
      action(item);
  }
}
38
Samuel

エリック・リッパートによると、これは 主に哲学的な理由による です。あなたは投稿全体を読むべきですが、私が関係している限り、ここに要点があります:

私は、2つの理由から、そのような方法を提供することに哲学的に反対しています。

最初の理由は、これを行うと、他のすべてのシーケンス演算子が基づく関数型プログラミングの原則に違反するためです。明らかに、このメソッドの呼び出しの唯一の目的は、副作用を引き起こすことです。

式の目的は、副作用を引き起こすことではなく、値を計算することです。ステートメントの目的は、副作用を引き起こすことです。このことの呼び出しサイトは、式のようにひどく見えます(確かに、メソッドはvoidを返すため、式は「ステートメント式」コンテキストでのみ使用できます)。

副作用のためだけに役立つ唯一のシーケンス演算子を作ることは、私にはうまく合いません。

2番目の理由は、そうすることで言語に新しい表現力が追加されないためです。

39
Justin R.

なぜなら、IEnumerableのForEach()は、次のように各ループの正常な動作だからです。

for each T item in MyEnumerable
{
    // Action<T> goes here
}
5
Joel Coehoorn

私はここで推測しているだけですが、IEnumerableにforeachを設定すると、その操作で副作用が発生します。 「利用可能な」拡張メソッドはいずれも副作用を引き起こしません。foreachのような命令型メソッドをそこに置くと、APIが混乱するでしょう。また、foreachはレイジーコレクションを初期化します。

個人的には、副作用のない関数と副作用のない関数を区別するためだけに、自分のを追加するという誘惑から身を守っています。

3
Surya

正直なところ、なぜ.ForEach(Action)がIEnumerableに含まれていないのかはわかりませんが、正しい、間違っている、無関心である、というのがその方法です...

I DIDただし、他のコメントで言及されているパフォーマンスの問題を強調したい。コレクションをループする方法に基づくパフォーマンスヒットがあります。これは比較的小さいですが、それでも確かに存在します。ここにあります。関係を示す信じられないほど高速でずさんなコードスニペット...実行するのに1分ほどかかります。

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Start Loop timing test: loading collection...");
        List<int> l = new List<int>();

        for (long i = 0; i < 60000000; i++)
        {
            l.Add(Convert.ToInt32(i));
        }

        Console.WriteLine("Collection loaded with {0} elements: start timings",l.Count());
        Console.WriteLine("\n<===============================================>\n");
        Console.WriteLine("foreach loop test starting...");

        DateTime start = DateTime.Now;

        //l.ForEach(x => l[x].ToString());

        foreach (int x in l)
            l[x].ToString();

        Console.WriteLine("foreach Loop Time for {0} elements = {1}", l.Count(), DateTime.Now - start);
        Console.WriteLine("\n<===============================================>\n");
        Console.WriteLine("List.ForEach(x => x.action) loop test starting...");

        start = DateTime.Now;

        l.ForEach(x => l[x].ToString());

        Console.WriteLine("List.ForEach(x => x.action) Loop Time for {0} elements = {1}", l.Count(), DateTime.Now - start);
        Console.WriteLine("\n<===============================================>\n");

        Console.WriteLine("for loop test starting...");

        start = DateTime.Now;
        int count = l.Count();
        for (int i = 0; i < count; i++)
        {
            l[i].ToString();
        }

        Console.WriteLine("for Loop Time for {0} elements = {1}", l.Count(), DateTime.Now - start);
        Console.WriteLine("\n<===============================================>\n");

        Console.WriteLine("\n\nPress Enter to continue...");
        Console.ReadLine();
    }

ただし、これにあまり夢中にならないでください。パフォーマンスはアプリケーション設計の最新のものですが、アプリケーションで実際のパフォーマンスヒットが発生してユーザビリティの問題が発生しているのでない限り、時間は実際のビジネスプロジェクトの最新のものであるため、保守性と再利用のコーディングに集中してください...

2
Aaron

ForEachはIListにありません。リストにあります。あなたの例では具体的なリストを使用していました。

2
Joshua Belden

ForEachは具象クラスList<T>に実装されています

0
Chad Grant

IEnumerable<T>では「選択」と呼ばれます 私は悟りました、ありがとう。

0
JP Alioto

推測にすぎませんが、Listは列挙子を作成せずに項目を反復できます。

public void ForEach(Action<T> action)
{
    if (action == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
    }
    for (int i = 0; i < this._size; i++)
    {
        action(this._items[i]);
    }
}

これにより、パフォーマンスが向上します。 IEnumerableでは、通常のforループを使用するオプションはありません。

0
Rauhotz

LINQはプルモデルに従い、ToList()を除くすべての(拡張)メソッドは_IEnumerable<T>_を返す必要があります。 ToList()はプルチェーンを終了するためにあります。

ForEach()は、プッシュモデルの世界のものです。

Samuelが指摘したように、独自の拡張メソッドを作成してこれを行うこともできます。

0
tofi9