Linq扩展方法获取单个元素

在使用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

时间: 2024-11-23 11:40:39

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

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

文件目录:aspnetboilerplate-dev\aspnetboilerplate-dev\src\Abp\Collections\Extensions\EnumerableExtensions.cs using System; using System.Collections.Generic; using System.Linq; namespace Abp.Collections.Extensions { /// <summary> /// Extension methods for

使用Linq扩展方法时容易忽略的小问题

问题重现 下面直接给出用于说明文章主题的完整代码. //************************************************************ // // Linq扩展方法示例代码 // // Author:三五月儿 // // Date:2014/10/01 // // http://blog.csdn.net/yl2isoft // //************************************************************

LinQ—扩展方法

概述 本节主要讲解扩展方法,涉及LinQ的具体知识不多. 扩展方法的描述 .net framework为编程人员提供了很多的类,很多的方法,但是,不论.net framework在类中为我们提供了多么多的方法,有时候仍然不能满足我们的需求,例如:你想让字符串对象具有ToPascal方法,含义就是将字符串转化为Pascal格式,并返回,我们知道,.net framework提供的String类中并没有为我们提供相应的方法,此时,我们应该怎么做才可以达到我们的目的呢?有人说可以继承String类,这

ToDictionary()LINQ扩展方法

ToList() 使用IEnumerable<T>并将其转换为 List<T>,那么 ToDictionary()也是类似的.大多数情况ToDictionary()是一个非常方便的方法,将查询的结果(或任何 IEnumerable<T>)转换成一个Dictionary<TKey,TValue>. 关键是您需要定义T如何分别转换TKey和TValue. 如果说我们有超级大的产品列表,希望把它放在一个Dictionary<int, product>,

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

获取某个元素上的事件,jquery的给元素绑定的事件可以用data方法取出来. 通过$(element).data("events")来获取 // 比如给一个button绑定两个click事件 $("button").click(function() { alert("1") }); $("button").click(function() { alert("2") }); // 这个时候点击该button

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

Json.NET的JObject明明实现了IEnumerable<T>,具体来说是IEnumerable<KeyValuePair<string, JToken>>,按说JObject类型的对象是可以直接调用Select.Where等linq扩展方法的,但偏偏就是不行,代码如下: using System.Linq; ... var jobj = new JObject(); var xxx = jobj.Select(x=>x); //报错:JObject未包含

Linq入门——什么是linq &amp; 扩展方法

一,什么是Linq linq(language integrated Query):语言集成查询: linq包含如下: 对对象的查询,对数据库的查询,对XML的查询. 那么,没有linq前我们是怎样查询的? 先看一个例子: 现在我们要查询大于50的数,: 在没有linq之前,我们的代码时这样的: 使用了linq查询: 首先,从直观上看,代码更加简洁,其次,对于查询部分,接近SQL语句,层次清晰,容易理解: 除了简单高效以为,LINQ的出现解决了很多问题: 1,面向对象与数据访问两个领域长期分裂,

枚举扩展方法获取枚举Description

枚举扩展方法 1 /// <summary> 2 /// 扩展方法,获得枚举的Description 3 /// </summary> 4 /// <param name="value">枚举值</param> 5 /// <param name="nameInstend">当枚举没有定义DescriptionAttribute,是否用枚举名代替,默认使用</param> 6 /// <r

Linq扩展方法之All 、Any

// Summary: // 确定序列中的所有元素是否满足条件. // Parameters: // source:包含要应用谓词的元素的 System.Collections.Generic.IEnumerable`1. // predicate:用于测试每个元素是否满足条件的函数. // Type parameters: // TSource:source 中的元素的类型. // Returns:如果源序列中的每个元素都通过指定谓词中的测试,或者序列为空,则为 true:否则为 false.