ProgramingTip

둘 이상의 항목이 이미 발견 된 경우에도 Enumerable.Single ()이 모든 요소를 ​​반복하는 이유는 무엇입니까?

bestdevel 2020. 11. 25. 08:14
반응형

둘 이상의 항목이 이미 발견 된 경우에도 Enumerable.Single ()이 모든 요소를 ​​반복하는 이유는 무엇입니까?


애플리케이션 중 하나를 약력 링 할 때 Enumerable.Single(source, predicate)컬렉션 시작 부분에 술어와 일치하는 항목이 두 개 이상있는 존재 컬렉션을 호출하는 일부 코드에서 신비한 속도를 발견 했습니다.

조사에 따르면 의 구현은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();
}

이 경우 .NET Framework에 대한 최적화를 추가하기 위해 노력했습니다

IList.


당신 만이 그렇게 생각하는 것 같지는 않았습니다. .NET 코어 구현 에 최적화 된 버전이 :

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;
        }
    }
}

따라서 귀하의 질문에 답하기 위해 : 개발자가이 사용 사례를 최적화하는 것에 대해 생각하지 않는 것 외에는 '좋은'이유가없는 것 같습니다.


최적화 는 .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 출력을 확인하면 반복 최적화가 2016 년에 다시 적용되었음을 알 수 있습니다!

IList<>최적화는 아마도 코어 2.1 최적화의 일환으로, 1 년 전 하였다


다른 답변에서 지적했듯이 최적화가 적용되었지만 술어 함수에 사이드가 없다는 것을 보장 할 방법이 없다는 사실을 원래 생각하면서 그렇게 한 가설을 제기하고 싶습니다. 효과.

그런 행동이 실제로 사용 / 유용한 경우가 있을지는 모르겠지만 명심해야 할 고려 사항입니다.

참고 URL : https://stackoverflow.com/questions/53042806/why-does-enumerable-single-iterate-all-elements-even-when-more-than-one-item

반응형