Find()与FirstOrDefault()的性能

可能重复:
Find()与Where()。FirstOrDefault()

有一个有趣的结果search戴安娜在一个简单的引用types具有单个string属性的大序列。

Stopwatch watch = new Stopwatch(); 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()没有枚举器的开销,或者这加上也许别的东西?

Find()运行速度几乎快了一倍,希望.Net团队不会标记它将来会被淘汰。

我能够模仿你的结果,所以我反编译你的程序,有一个区别FindFirstOrDefault

首先在这里是反编译的程序。 我把你的数据对象作为一个匿名数据项目来编译

  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); 

这里要注意的关键是FirstOrDefaultEnumerable上被调用,而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); } 

所以它迭代了一个有意义的项目数组,因为列表是一个数组的包装。

但是, Enumerable类中的FirstOrDefault使用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只是使用可枚举模式的syntally糖 。 看这个图像

在这里输入图像说明

我点击foreach看看它在做什么,你可以看到dotpeek想带我到枚举器/当前/下一个实现是有道理的。

除此之外,它们基本上是相同的(testing传入谓词以查看某个项目是否是您想要的)

FirstOrDefaultFirstOrDefault正在通过IEnumerable实现运行,也就是说,它将使用标准的foreach循环来执行检查。 List<T>.Find()不是Linq( http://msdn.microsoft.com/en-us/library/x0b5b5bc.aspx )的一部分,可能使用从0Count (或另一个快速的内部机制可能直接操作其内部/包装arrays)。 通过摆脱枚举的开销(并进行版本检查以确保列表未被修改), Find方法更快。

如果你添加第三个testing:

 //3. System.Collections.Generic.List<T> foreach Func<Customer, bool> dianaCheck = c => c.Name == diana; watch.Restart(); foreach(var c in customers) { if (dianaCheck(c)) break; } watch.Stop(); Console.WriteLine("Diana was found in {0} ms with System.Collections.Generic.List<T> foreach.", watch.ElapsedMilliseconds); 

运行速度与第一个相同(25ms vs FirstOrDefault为FirstOrDefault

编辑:如果我添加一个数组循环,它非常接近的Find()速度,并给予@devshorts在源代码偷看,我认为这是它:

 //4. System.Collections.Generic.List<T> for loop var customersArray = customers.ToArray(); watch.Restart(); int customersCount = customersArray.Length; for (int i = 0; i < customersCount; i++) { if (dianaCheck(customers[i])) break; } watch.Stop(); Console.WriteLine("Diana was found in {0} ms with an array for loop.", watch.ElapsedMilliseconds); 

这只比Find()方法慢5.5%。

所以底线:循环数组元素比处理foreach迭代开销更快。 (但是两者都有其优点/缺点,所以只需从逻辑上select对代码有意义的内容,而且很less有速度差异会导致问题,所以只需使用可维护性/可读性)