Don't Repeat Yourself に従って常に繰り返すコードがある場合は、それを自分のライブラリに入れて呼び出す必要があります。それを念頭に置いて、ここで正しい答えを得るには2つの側面があります. 1 つ目は、ライブラリ関数を呼び出すコードの明確さと簡潔さです。2 つ目は、foreach のパフォーマンスへの影響です。
まず、呼び出しコードの明快さと簡潔さについて考えてみましょう。
foreach はさまざまな方法で実行できます。
- for ループ
- foreach ループ
- Collection.ForEach
ランバで foreach List.ForEach を実行するすべての方法の中で、最も明確で簡潔です。
list.ForEach(i => Console.Write("{0}\t", i));
したがって、この段階では、List.ForEach が最適なように見えるかもしれません。しかし、これのパフォーマンスは何ですか?この場合、コンソールに書き込む時間がコードのパフォーマンスを左右することは事実です。特定の言語機能のパフォーマンスについて何か知っている場合は、少なくともそれを考慮する必要があります。
ダストン キャンベルの foreach のパフォーマンス測定によると、最適化されたコードの下でリストを反復処理する最速の方法は、List.Count を呼び出さずに for ループを使用することです。
ただし、for ループは冗長な構造です。また、機能的なイディオムに向かう現在の傾向とは一致しない、非常に反復的な方法と見なされています。
では、簡潔さ、明快さ、パフォーマンスを得ることができるでしょうか? 拡張メソッドを使用してできます。理想的な世界では、リストを取得して区切り記号で書き込む拡張メソッドをコンソールで作成します。Console は静的クラスであり、拡張メソッドはクラスのインスタンスでのみ機能するため、これを行うことはできません。代わりに、リスト自体に拡張メソッドを配置する必要があります (David B の提案に従って):
public static void WriteLine(this List<int> theList)
{
foreach (int i in list)
{
Console.Write("{0}\t", t.ToString());
}
Console.WriteLine();
}
このコードは多くの場所で使用されるため、次の改善を行う必要があります。
- foreach を使用する代わりに、キャッシュされたカウントを持つ for ループであるコレクションを反復する最速の方法を使用する必要があります。
- 現在、引数として渡すことができるのは List のみです。ライブラリ関数として、少しの努力でそれを一般化できます。
- List を使用すると、リストだけに制限されます。IList を使用すると、このコードは配列でも動作します。
- 拡張メソッドは IList にあるため、名前を変更して、何に書き込んでいるのかを明確にする必要があります。
関数のコードは次のようになります。
public static void WriteToConsole<T>(this IList<T> collection)
{
int count = collection.Count();
for(int i = 0; i < count; ++i)
{
Console.Write("{0}\t", collection[i].ToString(), delimiter);
}
Console.WriteLine();
}
クライアントが区切り文字を渡せるようにすることで、これをさらに改善できます。次に、次のような標準の区切り文字を使用してコンソールに書き込む 2 つ目の関数を提供できます。
public static void WriteToConsole<T>(this IList<T> collection)
{
WriteToConsole<T>(collection, "\t");
}
public static void WriteToConsole<T>(this IList<T> collection, string delimiter)
{
int count = collection.Count();
for(int i = 0; i < count; ++i)
{
Console.Write("{0}{1}", collection[i].ToString(), delimiter);
}
Console.WriteLine();
}
これで、リストをコンソールに書き込むための簡潔で明確なパフォーマンスの高い方法が必要になったので、1 つ用意しました。ライブラリ関数を使用したデモを含むソース コード全体を次に示します。
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleWritelineTest
{
public static class Extensions
{
public static void WriteToConsole<T>(this IList<T> collection)
{
WriteToConsole<T>(collection, "\t");
}
public static void WriteToConsole<T>(this IList<T> collection, string delimiter)
{
int count = collection.Count();
for(int i = 0; i < count; ++i)
{
Console.Write("{0}{1}", collection[i].ToString(), delimiter);
}
Console.WriteLine();
}
}
internal class Foo
{
override public string ToString()
{
return "FooClass";
}
}
internal class Program
{
static void Main(string[] args)
{
var myIntList = new List<int> {1, 2, 3, 4, 5};
var myDoubleList = new List<double> {1.1, 2.2, 3.3, 4.4};
var myDoubleArray = new Double[] {12.3, 12.4, 12.5, 12.6};
var myFooList = new List<Foo> {new Foo(), new Foo(), new Foo()};
// Using the standard delimiter /t
myIntList.WriteToConsole();
myDoubleList.WriteToConsole();
myDoubleArray.WriteToConsole();
myFooList.WriteToConsole();
// Using our own delimiter ~
myIntList.WriteToConsole("~");
Console.Read();
}
}
}
================================================== =====
これで答えは終わりだと思うかもしれません。ただし、さらに一般化できる部分があります。彼が常にコンソールに書き込みを行っているかどうかは、fatcat の質問からは明らかではありません。おそらく、foreach で何か他のことを行う必要があります。その場合、ジェイソン・バンティングの答えはその一般性を与えるでしょう。これが彼の答えです。
list.ForEach(i => Console.Write("{0}\t", i));
これは、拡張メソッドをさらに改良し、以下のように FastForEach を追加しない限りです。
public static void FastForEach<T>(this IList<T> collection, Action<T> actionToPerform)
{
int count = collection.Count();
for (int i = 0; i < count; ++i)
{
actionToPerform(collection[i]);
}
Console.WriteLine();
}
これにより、可能な限り最速の反復法を使用して、コレクション内のすべての要素に対して任意のコードを実行できます。
WriteToConsole 関数を FastForEach を使用するように変更することもできます
public static void WriteToConsole<T>(this IList<T> collection, string delimiter)
{
collection.FastForEach(item => Console.Write("{0}{1}", item.ToString(), delimiter));
}
したがって、FastForEach の使用例を含むソース コード全体は次のようになります。
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleWritelineTest
{
public static class Extensions
{
public static void WriteToConsole<T>(this IList<T> collection)
{
WriteToConsole<T>(collection, "\t");
}
public static void WriteToConsole<T>(this IList<T> collection, string delimiter)
{
collection.FastForEach(item => Console.Write("{0}{1}", item.ToString(), delimiter));
}
public static void FastForEach<T>(this IList<T> collection, Action<T> actionToPerform)
{
int count = collection.Count();
for (int i = 0; i < count; ++i)
{
actionToPerform(collection[i]);
}
Console.WriteLine();
}
}
internal class Foo
{
override public string ToString()
{
return "FooClass";
}
}
internal class Program
{
static void Main(string[] args)
{
var myIntList = new List<int> {1, 2, 3, 4, 5};
var myDoubleList = new List<double> {1.1, 2.2, 3.3, 4.4};
var myDoubleArray = new Double[] {12.3, 12.4, 12.5, 12.6};
var myFooList = new List<Foo> {new Foo(), new Foo(), new Foo()};
// Using the standard delimiter /t
myIntList.WriteToConsole();
myDoubleList.WriteToConsole();
myDoubleArray.WriteToConsole();
myFooList.WriteToConsole();
// Using our own delimiter ~
myIntList.WriteToConsole("~");
// What if we want to write them to separate lines?
myIntList.FastForEach(item => Console.WriteLine(item.ToString()));
Console.Read();
}
}
}