c# - 为什么 LINQ 方法 Any 不检查计数?

标签 c# .net linq

如果我们查看扩展方法 Any 的源代码,我们会发现它总是使用枚举器:

public static bool Any<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)
        throw Error.ArgumentNull(nameof (source));
    using (IEnumerator<TSource> enumerator = source.GetEnumerator())
    {
        if (enumerator.MoveNext())
            return true;
    }
    return false;
}

我认为,如果集合是 IList,例如在 SingleOrDefault 方法中,检查 Count 属性不是更好吗? :

public static TSource SingleOrDefault<TSource>(this IEnumerable<TSource> source)
{
    if (source == null)
        throw Error.ArgumentNull(nameof(source));
    IList<TSource> sourceList = source as IList<TSource>;
    if (sourceList != null)
    {
        switch (sourceList.Count)
        {
            case 0:
                return default(TSource);
            case 1:
                return sourceList[0];
        }
    }
    else
    {
        //...
    }
    throw Error.MoreThanOneElement();
}

我说,它可以看起来像这样:

private static bool Any<TSource>(IEnumerable<TSource> source)
{
    if (source == null)
        throw new ArgumentNullException(nameof(source));

    IList<TSource> sourceList = source as IList<TSource>;

    if (sourceList != null)
    {
        return sourceList.Count != 0;
    }

    using (IEnumerator<TSource> enumerator = source.GetEnumerator())
    {
        if (enumerator.MoveNext())
            return true;
    }
    return false;
}

我写了一个基准来测试它:

namespace AnyTests
{

    class Program
    {
        static void Main(string[] args)
        {
            BenchmarkRunner.Run<Test>();
        }
    }

    public class Test
    {
        private readonly List<int> list1 = new List<int>(new[] { 1, 2, 3, 4, 5 });

        private readonly IEnumerable<int> list2 = GetCollection();

        private static IEnumerable<int> GetCollection()
        {
            yield return 1;
        }

        [Benchmark]
        public void TestLinqAnyList()
        {
            Enumerable.Any(list1);
        }

        [Benchmark]
        public void TestNewAnyList()
        {
            NewAny(list1);
        }

        [Benchmark]
        public void TestLinqAnyEnumerable()
        {
            Enumerable.Any(list2);
        }

        [Benchmark]
        public void TestNewAnyEnumerable()
        {
            NewAny(list2);
        }


        private static bool NewAny<TSource>(IEnumerable<TSource> source)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));

            IList<TSource> sourceList = source as IList<TSource>;

            if (sourceList != null)
            {
                return sourceList.Count != 0;
            }

            using (IEnumerator<TSource> enumerator = source.GetEnumerator())
            {
                if (enumerator.MoveNext())
                    return true;
            }
            return false;
        }
    }
}

结果表明它的性能提高了大约两倍:

// * Summary *

BenchmarkDotNet=v0.10.13, OS=Windows 10 Redstone 3 [1709, Fall Creators Update] (10.0.16299.192)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical cores and 4 physical cores
Frequency=3515624 Hz, Resolution=284.4445 ns, Timer=TSC
  [Host]     : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.7.2600.0
  DefaultJob : .NET Framework 4.7.1 (CLR 4.0.30319.42000), 32bit LegacyJIT-v4.7.2600.0


                Method |     Mean |     Error |    StdDev |
---------------------- |---------:|----------:|----------:|
       TestLinqAnyList | 26.80 ns | 0.1382 ns | 0.1154 ns |
        TestNewAnyList | 12.75 ns | 0.0480 ns | 0.0426 ns |
 TestLinqAnyEnumerable | 18.03 ns | 0.0947 ns | 0.0886 ns |
  TestNewAnyEnumerable | 23.51 ns | 0.0913 ns | 0.0762 ns |

对于 IList,它大约好两倍,对于 IEnumerable,它大约差 20%。

那么,问题是:在 SingleOrDefault 方法中使用优化而不在 Any 方法中使用它的原因是什么?

最佳答案

您的问题背后的假设可能是:

Count is fast, why not use it?

为什么 Any 不使用它的一个合理的答案是 Count 并不总是快。他们选择的实现的优点是它会具有相对稳定和低成本(即大约 O(1))。 它可能不会在所有情况下都像 Count 一样快(正如您所确定的那样)。

没有保证实现IListICollection 的类将具有快速 计数 属性。 ConcurrentDictionary ,例如,Count > 0 的速度通常比现有的 Any 实现要慢。

此外,您使用 IList 的代码应该可能使用 ICollection,因为您的代码不需要 IList 的额外功能提供访问权限。

关于c# - 为什么 LINQ 方法 Any 不检查计数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49663052/

相关文章:

c# - 如何滚动到 UWP 中的元素

c# - 附加类型的实体失败,因为相同类型的另一个实体已经具有相同的主键值

.net - ASP.NET:为什么登录引用不包括端口

c# - 动态创建属性

c# - 未找到返回有效的 Azure 存储 URI

c# - Web Api 2 未将 ByteArrayContent 返回给 HTTPClient

c# - KeyVaultClient.AuthenticationCallback Delegate 的参数来自哪里?

c# - .net 接口(interface)与泛型方法,实现中的错误

c# - 动态构建 LINQ-To-Entities Where 子句

vb.net - LINQ-to-List 和 IEnumerable 问题