IList< T>の.ForEach()はなぜですか? IEnumerable< T>ではありませんか? [複製]
-
03-07-2019 - |
質問
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行目から最終行の .ForEach()
の前に挿入された .ToList()
に注意してください。
.ForEach()
が IEnumerable&lt; T&gt;
の拡張メソッドとして使用できないのはなぜですか?このような例では、奇妙に思えます。
解決
ForEach(Action)
は IEnumerable&lt; T&gt;
が存在する前に存在していました。
他の拡張メソッドでは追加されていないため、C#デザイナーはそれが悪いデザインであると感じ、 foreach
構造を好むと推測できます。
編集:
独自の拡張メソッドを作成したい場合、 List&lt; T&gt;
のメソッドをオーバーライドしませんが、 IEnumerable&lt; T&gtを実装する他のクラスでは機能します;
。
public static class IEnumerableExtensions
{
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
foreach (T item in source)
action(item);
}
}
他のヒント
Eric Lippertによると、これはほぼ哲学的な理由で。投稿全体を読む必要がありますが、私が懸念している限り、ここに要点があります:
私は哲学的に反対です そのような方法を2人に提供する 理由。
最初の理由はそうすることです 関数型プログラミングに違反する 他のすべてのシーケンスという原則 演算子はに基づいています。明らかに このメソッドの呼び出しの唯一の目的 副作用を引き起こすことです。
式の目的は 値を計算し、サイドを引き起こさない 効果。ステートメントの目的は 副作用を引き起こすため。呼び出しサイト この事の非常に多くに見えるだろう 式のように(ただし、 確かに、メソッドは 戻り値が空の場合、式は &#8220;ステートメントでのみ使用されます 式&#8221;コンテキスト。)
私と一緒に作るのは良くない 唯一のシーケンス演算子 それはその側でのみ有用です 効果。
2番目の理由はそうすることです ゼロの新しい表現力を追加します 言語に。
IEnumerableの ForEach()
は、次のように各ループで正常なためです:
for each T item in MyEnumerable
{
// Action<T> goes here
}
ここでは推測しているだけですが、foreachをIEnumerableに設定すると、操作が副作用を引き起こすことになります。 「利用可能」なものはありません。拡張メソッドは副作用を引き起こし、foreachのような命令的メソッドをそこに置くと、私が推測するAPIを濁らせるでしょう。また、foreachは遅延コレクションを初期化します。
個人的には、副作用のない機能を副作用のある機能から分離するためだけに、自分自身を追加したいという誘惑を避けてきました。
ForEachはIListではなく、Listにあります。例では具体的なリストを使用していました。
.ForEach(Action)がIEnumerableに含まれていない理由は確かではありませんが、正しい、間違っている、または無関心なのはそういうことです...
ただし、他のコメントで言及されているパフォーマンスの問題を強調したいのです。コレクションのループ方法に基づいてパフォーマンスが低下します。比較的マイナーですが、それでも確かに存在します。これは、関係を示すための非常に高速でずさんなコードスニペットです...実行するのに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は具象クラス List&lt; T&gt;
推測ではありますが、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ループを使用するオプションがありません。
&quot;選択&quot;と呼ばれます。
啓発されました、ありがとうございます。 IEnumerable&lt; T&gt;
LINQはpullモデルに従い、そのすべての(拡張)メソッドは ToList()
を除き、 IEnumerable&lt; T&gt;
を返す必要があります。 ToList()
はプルチェーンを終了するためにあります。
ForEach()
はプッシュモデルの世界のものです。
サミュエルが指摘したように、これを行うための独自の拡張メソッドを作成できます。