単一の文字列プロパティを持つ単純な参照型の大きなシーケンス内で Diana を検索したところ、興味深い結果が得られました。
using System;
using System.Collections.Generic;
using System.Linq;
public class Customer{
public string Name {get;set;}
}
Stopwatch watch = new Stopwatch();
const string diana = "Diana";
while (Console.ReadKey().Key != ConsoleKey.Escape)
{
//Armour with 1000k++ customers. Wow, should be a product with a great success! :)
var customers = (from i in Enumerable.Range(0, 1000000)
select new Customer
{
Name = Guid.NewGuid().ToString()
}).ToList();
customers.Insert(999000, new Customer { Name = diana }); // Putting Diana at the end :)
//1. System.Linq.Enumerable.DefaultOrFirst()
watch.Restart();
customers.FirstOrDefault(c => c.Name == diana);
watch.Stop();
Console.WriteLine("Diana was found in {0} ms with System.Linq.Enumerable.FirstOrDefault().", watch.ElapsedMilliseconds);
//2. System.Collections.Generic.List<T>.Find()
watch.Restart();
customers.Find(c => c.Name == diana);
watch.Stop();
Console.WriteLine("Diana was found in {0} ms with System.Collections.Generic.List<T>.Find().", watch.ElapsedMilliseconds);
}
これは、List.Find() に Enumerator のオーバーヘッドがないことが原因でしょうか、それともこれに加えて何か他の原因があるのでしょうか?
Find()
ほぼ2倍の速さで走り、。ネットチームは将来的にこれを廃止としてマークすることはありません。
ベストアンサー1
あなたの結果を模倣できたので、プログラムを逆コンパイルしたところ、Find
との間には違いがありましたFirstOrDefault
。
まず、デコンパイルされたプログラムです。データオブジェクトをコンパイル用に匿名データ項目にしました。
List<\u003C\u003Ef__AnonymousType0<string>> source = Enumerable.ToList(Enumerable.Select(Enumerable.Range(0, 1000000), i =>
{
var local_0 = new
{
Name = Guid.NewGuid().ToString()
};
return local_0;
}));
source.Insert(999000, new
{
Name = diana
});
stopwatch.Restart();
Enumerable.FirstOrDefault(source, c => c.Name == diana);
stopwatch.Stop();
Console.WriteLine("Diana was found in {0} ms with System.Linq.Enumerable.FirstOrDefault().", (object) stopwatch.ElapsedMilliseconds);
stopwatch.Restart();
source.Find(c => c.Name == diana);
stopwatch.Stop();
Console.WriteLine("Diana was found in {0} ms with System.Collections.Generic.List<T>.Find().", (object) stopwatch.ElapsedMilliseconds);
ここで注目すべき重要な点は、FirstOrDefault
が呼び出されるEnumerable
のに対し、Find
はソース リスト上のメソッドとして呼び出されることです。
では、findは何をしているのか?これはデコンパイルされたFind
メソッドである
private T[] _items;
[__DynamicallyInvokable]
public T Find(Predicate<T> match)
{
if (match == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
for (int index = 0; index < this._size; ++index)
{
if (match(this._items[index]))
return this._items[index];
}
return default (T);
}
リストは配列のラッパーなので、これは項目の配列を反復処理する意味があります。
しかし、クラスFirstOrDefault
ではEnumerable
、foreach
アイテムを反復処理するために を使用します。これはリストにイテレータを使用して次に移動します。あなたが見ているのはイテレータのオーバーヘッドだと思います。
[__DynamicallyInvokable]
public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
if (source == null)
throw Error.ArgumentNull("source");
if (predicate == null)
throw Error.ArgumentNull("predicate");
foreach (TSource source1 in source)
{
if (predicate(source1))
return source1;
}
return default (TSource);
}
foreachはただ合成糖列挙パターンの使用について。この画像を見てください
。
foreach をクリックしてそれが何をしているかを確認したところ、dotpeek が enumerator/current/next の実装に移動しようとしていることがわかります。これは理にかなっています。
それ以外は基本的に同じです(渡された述語をテストして、アイテムが必要なものかどうかを確認します)