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()
を使用できないのはなぜですか?このような例では、奇妙に思えます。
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);
}
}
エリック・リッパートによると、これは 主に哲学的な理由による です。あなたは投稿全体を読むべきですが、私が関係している限り、ここに要点があります:
私は、2つの理由から、そのような方法を提供することに哲学的に反対しています。
最初の理由は、これを行うと、他のすべてのシーケンス演算子が基づく関数型プログラミングの原則に違反するためです。明らかに、このメソッドの呼び出しの唯一の目的は、副作用を引き起こすことです。
式の目的は、副作用を引き起こすことではなく、値を計算することです。ステートメントの目的は、副作用を引き起こすことです。このことの呼び出しサイトは、式のようにひどく見えます(確かに、メソッドはvoidを返すため、式は「ステートメント式」コンテキストでのみ使用できます)。
副作用のためだけに役立つ唯一のシーケンス演算子を作ることは、私にはうまく合いません。
2番目の理由は、そうすることで言語に新しい表現力が追加されないためです。
なぜなら、IEnumerableのForEach()
は、次のように各ループの正常な動作だからです。
for each T item in MyEnumerable
{
// Action<T> goes here
}
私はここで推測しているだけですが、IEnumerableにforeachを設定すると、その操作で副作用が発生します。 「利用可能な」拡張メソッドはいずれも副作用を引き起こしません。foreachのような命令型メソッドをそこに置くと、APIが混乱するでしょう。また、foreachはレイジーコレクションを初期化します。
個人的には、副作用のない関数と副作用のない関数を区別するためだけに、自分のを追加するという誘惑から身を守っています。
正直なところ、なぜ.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();
}
ただし、これにあまり夢中にならないでください。パフォーマンスはアプリケーション設計の最新のものですが、アプリケーションで実際のパフォーマンスヒットが発生してユーザビリティの問題が発生しているのでない限り、時間は実際のビジネスプロジェクトの最新のものであるため、保守性と再利用のコーディングに集中してください...
ForEachはIListにありません。リストにあります。あなたの例では具体的なリストを使用していました。
ForEachは具象クラスList<T>
に実装されています
私は悟りました、ありがとう。IEnumerable<T>
では「選択」と呼ばれます
推測にすぎませんが、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ループを使用するオプションはありません。
LINQはプルモデルに従い、ToList()
を除くすべての(拡張)メソッドは_IEnumerable<T>
_を返す必要があります。 ToList()
はプルチェーンを終了するためにあります。
ForEach()
は、プッシュモデルの世界のものです。
Samuelが指摘したように、独自の拡張メソッドを作成してこれを行うこともできます。