在使用Linq 提供的扩展方法时,First(OrDefault), Single(OrDefault), Last(OrDefault)都具有返回单个元素的功能。MSDN对这些方法的描述只有功能说明,没有关于内部的相关实现的描述说明。
首先我们来看下MSDN上关于这些扩展方法的官方描述:
First: 返回序列中的第一个元素 。
FirstOrDefault: 返回序列中的第一个元素;如果未找到元素,则返回默认值。
Last:返回序列的最后一个元素。
LastOrDefault: 返回序列中的最后一个元素;如果未找到元素,则返回默认值。
Single: 返回序列的唯一元素;如果该序列并非恰好包含一个元素,则会引发异常。
SingleOrDefault:返回序列中的唯一元素;如果该序列为空,则返回默认值;如果该序列包含多个元素,此方法将引发异常。
这些方法功能类似,如果不仔细阅读说明,细细推敲,在实际运用中很容易造成误用,从而导致性能的损失。
为了彻底分清这些方法的区别,我们用代码来验证不同方法的执行结果。代码如下:
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; internal class Program { public class KeyValue { public string Key { get; set; } public int Value { get; set; } public override string ToString() { return string.Format("Key:{0} Value:{1}", Key, Value); } } private static readonly Stopwatch Watch = new Stopwatch(); private static void Main(string[] args) { IEnumerable<KeyValue> _sources; _sources = BuildNonUniqueSources(); //_sources = BuildUniqueSources(); const string key = "ZZZ"; //消除初始化等影响 ShowTest(_sources, m => m.Key == key, Enumerable.Last); Console.Clear(); ShowTest(_sources, m => m.Key == key, Enumerable.First); ShowTest(_sources, m => m.Key == key, Enumerable.FirstOrDefault); ShowTest(_sources, m => m.Key == key, Enumerable.Single); ShowTest(_sources, m => m.Key == key, Enumerable.SingleOrDefault); ShowTest(_sources, m => m.Key == key, Enumerable.Last); ShowTest(_sources, m => m.Key == key, Enumerable.LastOrDefault); Console.WriteLine("Press any key to exit..."); Console.ReadLine(); } private static IEnumerable<KeyValue> BuildNonUniqueSources() { var result = new List<KeyValue>(); for (int i = 0; i < 10000; i++) { for (int j = 65; j < 91; j++) { var obj = new KeyValue() { Key = string.Format("{0}{0}{0}", (char)j), Value = i }; result.Add(obj); } } return result; } private static IEnumerable<KeyValue> BuildUniqueSources() { var result = new List<KeyValue>(); for (int i = 0; i < 10000; i++) { for (int j = 65; j < 91; j++) { var obj = new KeyValue() { Key = string.Format("{0}{0}{0}-{1}", (char)j, i), Value = i }; result.Add(obj); } } return result; } private static void ShowTest(IEnumerable<KeyValue> sources, Func<KeyValue, bool> predicate, Func<IEnumerable<KeyValue>, Func<KeyValue, bool>, KeyValue> getKeyValueFunc) { var methodName = getKeyValueFunc.Method.Name; Console.Write("Method:{0} ", methodName); Watch.Restart(); try { Console.Write("Result:{0}", getKeyValueFunc(sources, predicate)); Watch.Stop(); } catch (InvalidOperationException invalidOptEx) { Console.Write("Exception:{0}", invalidOptEx.Message); } Console.WriteLine(" Total:{1}ms\n", methodName, Watch.Elapsed.TotalMilliseconds); } }
测试1、在Key值唯一的集合中查找单个对象
//_sources = BuildNonUniqueSources(); _sources = BuildUniqueSources(); const string key = "ZZZ-500";
测试结果如下
Method:First Result:Key:ZZZ-500 Value:500 Total:0.5157ms Method:FirstOrDefault Result:Key:ZZZ-500 Value:500 Total:0.4324ms Method:Single Result:Key:ZZZ-500 Value:500 Total:6.4474ms Method:SingleOrDefault Result:Key:ZZZ-500 Value:500 Total:6.5851ms Method:Last Result:Key:ZZZ-500 Value:500 Total:6.612ms Method:LastOrDefault Result:Key:ZZZ-500 Value:500 Total:6.4488ms
可以看到在查找唯一单个Key值时,First(OrDefault)运行时间最短,Single(OrDefault)和Last(OrDefault)运行时间差不多。
测试2、在Key值有重复的集合中查找单个对象
_sources = BuildNonUniqueSources(); //_sources = BuildUniqueSources(); const string key = "ZZZ";
测试结果如下
Method:First Result:Key:ZZZ Value:0 Total:0.1891ms Method:FirstOrDefault Result:Key:ZZZ Value:0 Total:0.1578ms Method:Single Exception:序列包含一个以上的匹配元素 Total:163.6677ms Method:SingleOrDefault Exception:序列包含一个以上的匹配元素 Total:7.1257ms Method:Last Result:Key:ZZZ Value:9999 Total:6.8112ms Method:LastOrDefault Result:Key:ZZZ Value:9999 Total:6.8662ms
当在元素有重复的集合中查找单个Key值时,First(OrDefault)运行时间依旧最短, Last(OrDefault)最长,Single(OrDefault)会抛出InvalidOperationException异常。
测试3、当Key并不包含在集合中时查找单个对象
_sources = BuildNonUniqueSources(); //_sources = BuildUniqueSources(); const string key = "???";
测试结果如下
Method:First Exception:序列不包含任何匹配元素 Total:6.8857ms Method:FirstOrDefault Result: Total:6.7131ms Method:Single Exception:序列不包含任何匹配元素 Total:6.772ms Method:SingleOrDefault Result: Total:6.8575ms Method:Last Exception:序列不包含任何匹配元素 Total:6.8167ms Method:LastOrDefault Result: Total:6.6318ms
查找的Key并不包含在集合中时,我们发现所有方法的运行时间区别不大。需要指出的是没有包含OrDefault的方法都抛出了InvalidOperationException异常。
总结
通过上面的测试,我们大致覆盖了实际使用中的多数场景,也了解了各个方法的差异。下一步我们来探究下这些方法内部的具体实现,好在.Net已经开源,我们可以很容易的查看到内部实现。
public static TSource First<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 element in source) { if (predicate(element)) return element; } throw Error.NoMatch(); } 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 element in source) { if (predicate(element)) return element; } return default(TSource); } public static TSource Last<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) { if (source == null) throw Error.ArgumentNull("source"); if (predicate == null) throw Error.ArgumentNull("predicate"); TSource result = default(TSource); bool found = false; foreach (TSource element in source) { if (predicate(element)) { result = element; found = true; } } if (found) return result; throw Error.NoMatch(); } public static TSource LastOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) { if (source == null) throw Error.ArgumentNull("source"); if (predicate == null) throw Error.ArgumentNull("predicate"); TSource result = default(TSource); foreach (TSource element in source) { if (predicate(element)) { result = element; } } return result; } public static TSource Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) { if (source == null) throw Error.ArgumentNull("source"); if (predicate == null) throw Error.ArgumentNull("predicate"); TSource result = default(TSource); long count = 0; foreach (TSource element in source) { if (predicate(element)) { result = element; checked { count++; } } } switch (count) { case 0: throw Error.NoMatch(); case 1: return result; } throw Error.MoreThanOneMatch(); } public static TSource SingleOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) { if (source == null) throw Error.ArgumentNull("source"); if (predicate == null) throw Error.ArgumentNull("predicate"); TSource result = default(TSource); long count = 0; foreach (TSource element in source) { if (predicate(element)) { result = element; checked { count++; } } } switch (count) { case 0: return default(TSource); case 1: return result; } throw Error.MoreThanOneMatch(); }
从上面的代码我们可以看到,所有方法的查找都是顺序查找,First(OrDefault)在查找时,当查找到满足条件的元素时会返回第一个元素。Single(OrDefault)和Last(OrDefault)在查找时,无论查找是否满足条件都会遍历整个集合;Single(OrDefault)在遍历时会对匹配的结果进行计数,用于判断结果是否唯一。带有OrDefault的方法在没有查找到指定条件时,会返回一个默认值default(TSource);与之对应的是无OrDefault的方法在遍历完集合都没有找到满足条件的元素时会抛出InvalidOperationException异常。
扩展方法 | 条件匹配(所有元素唯一) | 条件匹配(集合中元素有重复) | 条件不匹配 | 查找次数 |
First | 返回匹配的元素 | 返回匹配的元素 | 抛出InvalidOperationException | 1-N |
FirstOrDefault | 返回匹配的元素 | 返回匹配的元素 | 返回default(TSource) | 1-N |
Single | 返回匹配的元素 | 唯一匹配时返回该元素,多个匹配时抛出InvalidOperationException | 抛出InvalidOperationException | N |
SingleOrDefault | 返回匹配的元素 | 唯一匹配时返回该元素,多个匹配时抛出InvalidOperationException | 返回default(TSource) | N |
Last | 返回匹配的元素 | 返回匹配的元素 | 抛出InvalidOperationException | N |
LastOrDefault | 返回匹配的元素 | 返回匹配的元素 | 返回default(TSource) | N |
相关资料
https://msdn.microsoft.com/zh-cn/library/vstudio/system.linq.enumerable_methods%28v=vs.100%29.aspx
http://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs