在使用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 = ; i < ; i++)
{
for (int j = ; j < ; 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 = ; i < ; i++)
{
for (int j = ; j < ; 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 = ;
foreach (TSource element in source)
{
if (predicate(element))
{
result = element;
checked { count++; }
}
}
switch (count)
{
case : throw Error.NoMatch();
case : 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 = ;
foreach (TSource element in source)
{
if (predicate(element))
{
result = element;
checked { count++; }
}
}
switch (count)
{
case : return default(TSource);
case : 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

Linq扩展方法获取单个元素的更多相关文章

  1. Linq扩展方法之Aggregate 对序列应用累加器函数

    Linq扩展方法之Aggregate  对序列应用累加器函数; 函数模板:// 函数名:对序列应用累加器函数. // Parameters:参数要求 // source:要聚合的 System.Col ...

  2. 【手记】走近科学之为什么明明实现了IEnumerable<T>的类型却不能调用LINQ扩展方法

    比如Json.NET的JObject明明实现了IEnumerable<T>,具体来说是IEnumerable<KeyValuePair<string, JToken>&g ...

  3. 用LinQ扩展方法,泛型扩展方法,实现自定义验证字符是否空、对象是否为null,及泛型约束使用,Action的使用

    一.Linq扩展方法 1.扩展方法必须是静态方法.扩展方法所在的类必须是静态类 2.扩展方法里面的参数必须制定this关键字,紧跟需要扩展的类型,如下: 二.泛型约束 1.使用泛型的原因,是在不知道需 ...

  4. ABP框架源码中的Linq扩展方法

    文件目录:aspnetboilerplate-dev\aspnetboilerplate-dev\src\Abp\Collections\Extensions\EnumerableExtensions ...

  5. 【手记】走近科学之为什么JObject不能调用LINQ扩展方法

    Json.NET的JObject明明实现了IEnumerable<T>,具体来说是IEnumerable<KeyValuePair<string, JToken>> ...

  6. Jquery学习笔记:利用find和children方法获取后代元素

    在很多场景下,需要根据一个已知的jquery对象,去查找其满足条件的后代节点. 这时可以利用 find函数和children来处理. find和children函数都可有一个参数,常见的是一个字符串, ...

  7. LinQ—扩展方法

    概述 本节主要解说扩展方法,涉及LinQ的详细知识不多. 扩展方法的描写叙述 .net framework为编程人员提供了非常多的类,非常多的方法,可是,不论.net framework在类中为我们提 ...

  8. jquery data方法获取某个元素上事件

    获取某个元素上的事件,jquery的给元素绑定的事件可以用data方法取出来. 通过$(element).data("events")来获取 // 比如给一个button绑定两个c ...

  9. 浮动产生的高度坍塌解决方法以及使用siblings()方法获取同级元素

    高度坍塌:如果一个没有设置高度div里的元素都是浮动元素,这个时候就可能产生高度坍塌,因为div的高度都是普通元素撑起来的,div里的元素浮动之后,元素就会脱离文档流,所以父级的div高度就可能为零, ...

随机推荐

  1. requests-html库render方法的使用

    一.render的使用 from requests_html import HTMLSession session =HTMLSession() response = session.get('htt ...

  2. 「Luogu P1210」回文检测 解题报告

    题面 这是一道诡异的黄题 居然让你求一串吧啦吧啦的东西中 字母(大小写)最长的回文串的长度,还要输出完整的串 吐血 思路: 保持淡定,我们啥都不会,就会Manacher,那就用Manacher大法! ...

  3. 「Luogu P2201」数列编辑器 解题报告

    数列编辑器,在线IDE 本期的主题是洛谷的在线IDE 小学生?!小学生虐我

  4. k8s-自动安装

    操作环境: centos7.3 node102-master-192.168.100.102 node103-node1-192.168.100.103 node104-node2-192.168.1 ...

  5. 用积分方法求K次方和数列公式

    这是我很早以前在高中时发现的一个通用计算K次方和数列公式的方法,很特别的地方是用了微积分中的积分方法.目前我还没有发现有谁提出和我一样的方法,如果哪位读者有相关发现,麻烦告知我. 大家很多人都知道高斯 ...

  6. Spring Cloud Alibaba Nacos

    1. Spring Cloud Alibaba 介绍 Spring Cloud Alibaba 为分布式应用程序开发提供了一站式解决方案.它包含了开发分布式应用程序所需的所有组件,使得你可以轻松地使用 ...

  7. vue实现网络图片瀑布流 + 下拉刷新 + 上拉加载更多

    一.思路分析和效果图 用vue来实现一个瀑布流效果,加载网络图片,同时有下拉刷新和上拉加载更多功能效果.然后针对这几个效果的实现,捋下思路: 根据加载数据的顺序,依次追加标签展示效果: 选择哪种方式实 ...

  8. vue状态管理vuex从浅入深详细讲解

    1.vuex简介以及创建一个简单的仓库 vuex是专门为vue框架而设计出的一个公共数据管理框架,任何组件都可以通过状态管理仓库数据沟通,也可以统一从仓库获取数据,在比较大型的应用中,数据交互庞大的情 ...

  9. 微信小程序----日期时间选择器(自定义精确到分秒或时段)

    声明 bug:由于此篇博客是在bindcolumnchange事件中做的值的改变处理,因此会出现当你选择时,没有点击确定,直接取消返回后,会发现选择框的值依然改变.造成原因:这一点就是由于在bindc ...

  10. STM32动态内存分配需要注意的地方

    STM32进行动态内存分配是需要注意动态内存分配大小不要超过.S文件中设置Heap Size大小 如图所示: 0x4000 :可以分配得最大字节是16384bytes 这个地方malloc的大小超过了 ...