在使用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. 二、webdriver API

    目录 1. webdriver中常用属性 2. 浏览器页面操作 3. 鼠标操作 4. 键盘操作 5. 下拉框操作 1. webdriver中常用属性 import time from selenium ...

  2. 「CodeForces 476A」Dreamoon and Stairs

    Dreamoon and Stairs 题意翻译 题面 DM小朋友想要上一个有 \(n\) 级台阶的楼梯.他每一步可以上 \(1\) 或 \(2\) 级台阶.假设他走上这个台阶一共用了 \(x\) 步 ...

  3. C#操作注册表(简单方便,兼容X32和X64)

    C#操作注册表(简单方便,兼容X32和X64) 大家好,我在这里给大家介绍本人实现的操作注册表的类,简单方便,兼容32位系统和64位系统. 一般大家用C#操作注册的方法是使用命名空间Microsoft ...

  4. 25.xlrd、xlwt和openpyxl模块的比较和使用

    xlrd.xlwt和openpyxl模块的比较:1)xlrd:对xls.xlsx.xlsm文件进行读操作–读操作效率较高,推荐2)xlwt:对xls文件进行写操作–写操作效率较高,但是不能执行xlsx ...

  5. Freemarker 的基础使用 (二)

    freemarker 的基础使用二 ftl 文件 <html> <head> <meta http-equiv="Content-Type" cont ...

  6. 每日一问2:堆(heap)和栈(stack)的区别

    因为这里没有明确指出堆是指数据结构还是存储方式,所以两个尝试都回答一下. 一.堆和栈作为数据结构 1.堆(heap),也叫做优先队列(priority queue),队列中允许的操作是先进先出(FIF ...

  7. CF825G Tree Queries

    [题意] 一棵树有 n个节点,初始均为白色,有两种操作: 1. 1 x 代表把结点 x 设置为黑色 2. 2 x 代表查询 x 到树上任意一个黑色结点的简单路径上的编号最小的结点的编号 输入 t 和 ...

  8. axios中请求传值方式

    日常开发中与后端联调,可能需要的数据不同,所传值也有所不同 1.如果是data方式,设置请求头为:并且直接返回data就可以  raw axios.defaults.headers['Content- ...

  9. Akka Java 中文文档

    Akka Java 中文文档 Introduction What is Akka? | 什么是Akka? Why Akka? | 为什么选择Akka? Getting Started | Akka入门 ...

  10. 设计模式(Java语言)- 工厂方法模式

    前言 在介绍工厂方法模式之前,我们需要知道这个设计模式是什么,解决了什么样的问题?在上一篇博客 设计模式(Java语言)- 简单工厂模式 介绍了简单工厂模式,然后总结了简单工厂模式的缺点: 1.当新增 ...