zoukankan      html  css  js  c++  java
  • 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
    ", methodName, Watch.Elapsed.TotalMilliseconds);
            }
        }
    View Code

    测试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();
            }
    View Code

    从上面的代码我们可以看到,所有方法的查找都是顺序查找,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

    作者: 棠城夜雨
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    多线程008如何预防死锁
    多线程003volatile的可见性和禁止指令重排序怎么实现的
    多线程011线程池运行原理及复用原理
    多线程010为什么要使用线程池
    多线程009什么是守护线程
    多线程005创建线程有哪些方式
    多线程007描述一下线程安全活跃态问题,以及竞态条件
    多线程002ThreadLocal有哪些内存泄露问题,如何避免
    关于Tomcat的启动时机(精通Eclipse Web开发P40)
    乱侃~~~
  • 原文地址:https://www.cnblogs.com/amour/p/4801012.html
Copyright © 2011-2022 走看看