c# क्यों Enumerable.Single() सभी तत्वों को पुनरावृत्त करता है, तब भी जब एक से अधिक आइटम पहले ही मिल चुके हों?




linq .net-4.0 (3)

जब हमारे अनुप्रयोगों में से एक की रूपरेखा तैयार करते हैं, तो हमने कुछ कोड में एक रहस्यमय मंदी की खोज की, जहां हम 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();
}

इस मामले में, वे IList लिए एक अनुकूलन जोड़ने के प्रयास में चले गए हैं।


अनुकूलन .NET कोर में लागू किया गया था

अब कोड है:

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

अद्यतन करें

गिट दोष उत्पादन की जाँच से पता चलता है कि 2016 में पुनरावृत्ति अनुकूलन लागू किया गया था!

IList<> अनुकूलन को 1 साल पहले जोड़ा गया था, शायद कोर 2.1 अनुकूलन के हिस्से के रूप में


आपको लगता है कि केवल एक ही सोच नहीं थी। .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-4.0