代码之家  ›  专栏  ›  技术社区  ›  Matthew Watson

为什么Enumerable.Single()迭代所有元素,即使已经找到多个项?

  •  63
  • Matthew Watson  · 技术社区  · 6 年前

    Enumerable.Single(source, predicate)

    调查显示 the implementation of Enumerable.Single() 具体如下:

    public static TSource Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) 
    {
            TSource result = default(TSource);
            long count = 0;
            // Note how this always iterates through ALL the elements:
            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 Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
    {
        TSource result = default(TSource);
        long count = 0;
        foreach (TSource element in source) {
            if (predicate(element)) {
                if (count == 1) // Exit loop immediately if more than one match found.
                    throw Error.MoreThanOneMatch();
    
                result = element;
                count++; // "checked" is no longer needed.
            }
        }
    
        if (count == 0)
            throw Error.NoMatch();
    
        return result;
    }
    

    有人知道为什么实际的实现没有使用这种明显的优化吗?有什么我不知道的吗(我无法想象这样一个明显的优化会被忽略,因此肯定有一些具体的原因。)

    (注:我认识到,这个问题可能会吸引一些意见的答案;我希望答案能为迭代所有元素提供具体的理由。如果答案是“因为设计师认为这样的优化是没有必要的”,那么这个问题是无法回答的,我想我应该删除它……)


    为了进行比较,请看 Single() 它不接受谓词:

    public static TSource Single<TSource>(this IEnumerable<TSource> source) 
    {
        IList<TSource> list = source as IList<TSource>;
        if (list != null) {
            switch (list.Count) {
                case 0: throw Error.NoElements();
                case 1: return list[0];
            }
        }
        else {
            using (IEnumerator<TSource> e = source.GetEnumerator()) {
                if (!e.MoveNext()) throw Error.NoElements();
                TSource result = e.Current;
                if (!e.MoveNext()) return result;
            }
        }
        throw Error.MoreThanOneElement();
    }
    

    IList .

    3 回复  |  直到 6 年前
        1
  •  40
  •   Patrick Hofman Wahid Bitar    6 年前

    .NET Core implementation 具有优化版本:

    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
        while (e.MoveNext())
        {
            TSource result = e.Current;
            if (predicate(result))
            {
                while (e.MoveNext())
                {
                    if (predicate(e.Current))
                    {
                        throw Error.MoreThanOneMatch();
                    }
                }
    
                return result;
            }
        }
    }
    

    所以回答你的问题:除了开发人员没有考虑优化这个用例之外,似乎没有什么“好”的理由。

        2
  •  10
  •   Panagiotis Kanavos    6 年前

    优化 was applied in .NET Core

    现在的代码是:

    public static TSource Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
    {
        if (source == null)
        {
            throw Error.ArgumentNull(nameof(source));
        }
    
        if (predicate == null)
        {
            throw Error.ArgumentNull(nameof(predicate));
        }
    
        using (IEnumerator<TSource> e = source.GetEnumerator())
        {
            while (e.MoveNext())
            {
                TSource result = e.Current;
                if (predicate(result))
                {
                    while (e.MoveNext())
                    {
                        if (predicate(e.Current))
                        {
                            throw Error.MoreThanOneMatch();
                        }
                    }
    
                    return result;
                }
            }
        }
    
        throw Error.NoMatch();
    }
    

    只要有可能,代码甚至会检查目标是否是 IList<T>

    public static TSource Single<TSource>(this IEnumerable<TSource> source)
    {
        if (source == null)
        {
            throw Error.ArgumentNull(nameof(source));
        }
    
        if (source is IList<TSource> list)
        {
            switch (list.Count)
            {
                case 0:
                    throw Error.NoElements();
                case 1:
                    return list[0];
            }
        }
        else
        {
            using (IEnumerator<TSource> e = source.GetEnumerator())
            {
                if (!e.MoveNext())
                {
                    throw Error.NoElements();
                }
    
                TSource result = e.Current;
                if (!e.MoveNext())
                {
                    return result;
                }
            }
        }
    
        throw Error.MoreThanOneElement();
    }
    

    更新

    检查 git blame

    这个 IList<>

        3
  •  5
  •   Peter Mortensen icecrime    6 年前

    正如其他答案所指出的,优化已经被应用,但我想提出一个假设,即他们是这样做的,最初的想法是,他们没有办法保证谓词函数没有副作用。

    我不确定这样的行为是否真的有用,但这是一个需要考虑的问题。