Why does Enumerable.Single() iterate all elements, even when more than one item has already been found?

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
11
down vote

favorite
3












When profiling one of our applications, we discovered a mysterious slowdown in some code where we were calling Enumerable.Single(source, predicate) for a large collection that had more than one item that matched the predicate near the start of the collection.



Investigation revealed that the implementation of Enumerable.Single() is as follows:



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



That implementation will iterate through every element of the sequence, even if more than one element has already matched the predicate.



The following implementation would appear to yield the same results:



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;



Does anyone know why the actual implementation doesn't use this obvious optimisation? Is there something I'm missing? (I can't imagine that such an obvious optimisation would be overlooked, and therefore there must be some concrete reason for it.)



(Note: I realise that this question may attract answers that are opinions; I'm hoping for answers that provide concrete reasons for iterating all elements. If the answer is actually "because the designers didn't think such an optimisation was necessary", then this question is unanswerable and I guess I should just delete it...)




For comparison, look at the implementation of Single() which does not take a predicate:



public static TSource Single(this IEnumerable 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();



In this case, they've gone to the effort of adding an optimisation for IList.










share|improve this question



















  • 1




    Optimizing failure paths is rarely worth doing.
    – Damien_The_Unbeliever
    32 mins ago










  • @Damien_The_Unbeliever In that case, why did they optimise SingleOrDefault<TSource>(this IEnumerable<TSource> source) to cast the source to IList and check the count directly?
    – Matthew Watson
    30 mins ago










  • What happens if an exception is thrown when enumerating the sequence after the second match? Returning early would change the behaviour. Which is the desired result is questionable though.
    – Sean Reid
    28 mins ago










  • @MatthewWatson - the IList optimizations are only for the predicate-free versions of both of these methods - where knowing the length of the list immediately answers the question. With the predicate, for the success case, it has to evaluate the predicate against every item anyway.
    – Damien_The_Unbeliever
    27 mins ago











  • See also codeblog.jonskeet.uk/2010/12/29/… - this uses your early out.
    – Sean Reid
    26 mins ago














up vote
11
down vote

favorite
3












When profiling one of our applications, we discovered a mysterious slowdown in some code where we were calling Enumerable.Single(source, predicate) for a large collection that had more than one item that matched the predicate near the start of the collection.



Investigation revealed that the implementation of Enumerable.Single() is as follows:



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



That implementation will iterate through every element of the sequence, even if more than one element has already matched the predicate.



The following implementation would appear to yield the same results:



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;



Does anyone know why the actual implementation doesn't use this obvious optimisation? Is there something I'm missing? (I can't imagine that such an obvious optimisation would be overlooked, and therefore there must be some concrete reason for it.)



(Note: I realise that this question may attract answers that are opinions; I'm hoping for answers that provide concrete reasons for iterating all elements. If the answer is actually "because the designers didn't think such an optimisation was necessary", then this question is unanswerable and I guess I should just delete it...)




For comparison, look at the implementation of Single() which does not take a predicate:



public static TSource Single(this IEnumerable 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();



In this case, they've gone to the effort of adding an optimisation for IList.










share|improve this question



















  • 1




    Optimizing failure paths is rarely worth doing.
    – Damien_The_Unbeliever
    32 mins ago










  • @Damien_The_Unbeliever In that case, why did they optimise SingleOrDefault<TSource>(this IEnumerable<TSource> source) to cast the source to IList and check the count directly?
    – Matthew Watson
    30 mins ago










  • What happens if an exception is thrown when enumerating the sequence after the second match? Returning early would change the behaviour. Which is the desired result is questionable though.
    – Sean Reid
    28 mins ago










  • @MatthewWatson - the IList optimizations are only for the predicate-free versions of both of these methods - where knowing the length of the list immediately answers the question. With the predicate, for the success case, it has to evaluate the predicate against every item anyway.
    – Damien_The_Unbeliever
    27 mins ago











  • See also codeblog.jonskeet.uk/2010/12/29/… - this uses your early out.
    – Sean Reid
    26 mins ago












up vote
11
down vote

favorite
3









up vote
11
down vote

favorite
3






3





When profiling one of our applications, we discovered a mysterious slowdown in some code where we were calling Enumerable.Single(source, predicate) for a large collection that had more than one item that matched the predicate near the start of the collection.



Investigation revealed that the implementation of Enumerable.Single() is as follows:



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



That implementation will iterate through every element of the sequence, even if more than one element has already matched the predicate.



The following implementation would appear to yield the same results:



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;



Does anyone know why the actual implementation doesn't use this obvious optimisation? Is there something I'm missing? (I can't imagine that such an obvious optimisation would be overlooked, and therefore there must be some concrete reason for it.)



(Note: I realise that this question may attract answers that are opinions; I'm hoping for answers that provide concrete reasons for iterating all elements. If the answer is actually "because the designers didn't think such an optimisation was necessary", then this question is unanswerable and I guess I should just delete it...)




For comparison, look at the implementation of Single() which does not take a predicate:



public static TSource Single(this IEnumerable 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();



In this case, they've gone to the effort of adding an optimisation for IList.










share|improve this question















When profiling one of our applications, we discovered a mysterious slowdown in some code where we were calling Enumerable.Single(source, predicate) for a large collection that had more than one item that matched the predicate near the start of the collection.



Investigation revealed that the implementation of Enumerable.Single() is as follows:



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



That implementation will iterate through every element of the sequence, even if more than one element has already matched the predicate.



The following implementation would appear to yield the same results:



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;



Does anyone know why the actual implementation doesn't use this obvious optimisation? Is there something I'm missing? (I can't imagine that such an obvious optimisation would be overlooked, and therefore there must be some concrete reason for it.)



(Note: I realise that this question may attract answers that are opinions; I'm hoping for answers that provide concrete reasons for iterating all elements. If the answer is actually "because the designers didn't think such an optimisation was necessary", then this question is unanswerable and I guess I should just delete it...)




For comparison, look at the implementation of Single() which does not take a predicate:



public static TSource Single(this IEnumerable 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();



In this case, they've gone to the effort of adding an optimisation for IList.







c# linq .net-4.0






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 11 mins ago









mjwills

13.7k42238




13.7k42238










asked 36 mins ago









Matthew Watson

70.9k688172




70.9k688172







  • 1




    Optimizing failure paths is rarely worth doing.
    – Damien_The_Unbeliever
    32 mins ago










  • @Damien_The_Unbeliever In that case, why did they optimise SingleOrDefault<TSource>(this IEnumerable<TSource> source) to cast the source to IList and check the count directly?
    – Matthew Watson
    30 mins ago










  • What happens if an exception is thrown when enumerating the sequence after the second match? Returning early would change the behaviour. Which is the desired result is questionable though.
    – Sean Reid
    28 mins ago










  • @MatthewWatson - the IList optimizations are only for the predicate-free versions of both of these methods - where knowing the length of the list immediately answers the question. With the predicate, for the success case, it has to evaluate the predicate against every item anyway.
    – Damien_The_Unbeliever
    27 mins ago











  • See also codeblog.jonskeet.uk/2010/12/29/… - this uses your early out.
    – Sean Reid
    26 mins ago












  • 1




    Optimizing failure paths is rarely worth doing.
    – Damien_The_Unbeliever
    32 mins ago










  • @Damien_The_Unbeliever In that case, why did they optimise SingleOrDefault<TSource>(this IEnumerable<TSource> source) to cast the source to IList and check the count directly?
    – Matthew Watson
    30 mins ago










  • What happens if an exception is thrown when enumerating the sequence after the second match? Returning early would change the behaviour. Which is the desired result is questionable though.
    – Sean Reid
    28 mins ago










  • @MatthewWatson - the IList optimizations are only for the predicate-free versions of both of these methods - where knowing the length of the list immediately answers the question. With the predicate, for the success case, it has to evaluate the predicate against every item anyway.
    – Damien_The_Unbeliever
    27 mins ago











  • See also codeblog.jonskeet.uk/2010/12/29/… - this uses your early out.
    – Sean Reid
    26 mins ago







1




1




Optimizing failure paths is rarely worth doing.
– Damien_The_Unbeliever
32 mins ago




Optimizing failure paths is rarely worth doing.
– Damien_The_Unbeliever
32 mins ago












@Damien_The_Unbeliever In that case, why did they optimise SingleOrDefault<TSource>(this IEnumerable<TSource> source) to cast the source to IList and check the count directly?
– Matthew Watson
30 mins ago




@Damien_The_Unbeliever In that case, why did they optimise SingleOrDefault<TSource>(this IEnumerable<TSource> source) to cast the source to IList and check the count directly?
– Matthew Watson
30 mins ago












What happens if an exception is thrown when enumerating the sequence after the second match? Returning early would change the behaviour. Which is the desired result is questionable though.
– Sean Reid
28 mins ago




What happens if an exception is thrown when enumerating the sequence after the second match? Returning early would change the behaviour. Which is the desired result is questionable though.
– Sean Reid
28 mins ago












@MatthewWatson - the IList optimizations are only for the predicate-free versions of both of these methods - where knowing the length of the list immediately answers the question. With the predicate, for the success case, it has to evaluate the predicate against every item anyway.
– Damien_The_Unbeliever
27 mins ago





@MatthewWatson - the IList optimizations are only for the predicate-free versions of both of these methods - where knowing the length of the list immediately answers the question. With the predicate, for the success case, it has to evaluate the predicate against every item anyway.
– Damien_The_Unbeliever
27 mins ago













See also codeblog.jonskeet.uk/2010/12/29/… - this uses your early out.
– Sean Reid
26 mins ago




See also codeblog.jonskeet.uk/2010/12/29/… - this uses your early out.
– Sean Reid
26 mins ago












2 Answers
2






active

oldest

votes

















up vote
10
down vote



accepted










You didn't seem to be the only one thinking that. The .NET Core implementation has an optimized version:



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;





So to answer your question: there doesn't seem to be a 'good' reason, other than just a developer not thinking about optimizing this use case.






share|improve this answer



























    up vote
    4
    down vote













    The optimization was applied in .NET Core



    The code now is :



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



    Wherever possible, the code even checks whether the target is an IList<T> so it can avoid iterating :
    public static TSource Single(this IEnumerable 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();



    UPDATE



    Checking the git blame output shows that the iteration optimization was applied back in 2016!



    The IList<> optimization was added 1 year ago, probably as part of the Core 2.1 optimizations






    share|improve this answer






















    • Indeed. You noticed the same as I did. :)
      – Patrick Hofman
      24 mins ago










    • Well this answers the question: It was actually a missed opportunity, which was corrected.
      – Matthew Watson
      23 mins ago










    • @MatthewWatson a lot of optimizations were added when 2.1 was introduced but Single.cs seems to go back to 2016
      – Panagiotis Kanavos
      22 mins ago










    Your Answer





    StackExchange.ifUsing("editor", function ()
    StackExchange.using("externalEditor", function ()
    StackExchange.using("snippets", function ()
    StackExchange.snippets.init();
    );
    );
    , "code-snippets");

    StackExchange.ready(function()
    var channelOptions =
    tags: "".split(" "),
    id: "1"
    ;
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function()
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled)
    StackExchange.using("snippets", function()
    createEditor();
    );

    else
    createEditor();

    );

    function createEditor()
    StackExchange.prepareEditor(
    heartbeatType: 'answer',
    convertImagesToLinks: true,
    noModals: false,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    );



    );













     

    draft saved


    draft discarded


















    StackExchange.ready(
    function ()
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53042806%2fwhy-does-enumerable-single-iterate-all-elements-even-when-more-than-one-item%23new-answer', 'question_page');

    );

    Post as a guest






























    2 Answers
    2






    active

    oldest

    votes








    2 Answers
    2






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    10
    down vote



    accepted










    You didn't seem to be the only one thinking that. The .NET Core implementation has an optimized version:



    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;





    So to answer your question: there doesn't seem to be a 'good' reason, other than just a developer not thinking about optimizing this use case.






    share|improve this answer
























      up vote
      10
      down vote



      accepted










      You didn't seem to be the only one thinking that. The .NET Core implementation has an optimized version:



      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;





      So to answer your question: there doesn't seem to be a 'good' reason, other than just a developer not thinking about optimizing this use case.






      share|improve this answer






















        up vote
        10
        down vote



        accepted







        up vote
        10
        down vote



        accepted






        You didn't seem to be the only one thinking that. The .NET Core implementation has an optimized version:



        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;





        So to answer your question: there doesn't seem to be a 'good' reason, other than just a developer not thinking about optimizing this use case.






        share|improve this answer












        You didn't seem to be the only one thinking that. The .NET Core implementation has an optimized version:



        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;





        So to answer your question: there doesn't seem to be a 'good' reason, other than just a developer not thinking about optimizing this use case.







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered 27 mins ago









        Patrick Hofman

        123k18162216




        123k18162216






















            up vote
            4
            down vote













            The optimization was applied in .NET Core



            The code now is :



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



            Wherever possible, the code even checks whether the target is an IList<T> so it can avoid iterating :
            public static TSource Single(this IEnumerable 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();



            UPDATE



            Checking the git blame output shows that the iteration optimization was applied back in 2016!



            The IList<> optimization was added 1 year ago, probably as part of the Core 2.1 optimizations






            share|improve this answer






















            • Indeed. You noticed the same as I did. :)
              – Patrick Hofman
              24 mins ago










            • Well this answers the question: It was actually a missed opportunity, which was corrected.
              – Matthew Watson
              23 mins ago










            • @MatthewWatson a lot of optimizations were added when 2.1 was introduced but Single.cs seems to go back to 2016
              – Panagiotis Kanavos
              22 mins ago














            up vote
            4
            down vote













            The optimization was applied in .NET Core



            The code now is :



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



            Wherever possible, the code even checks whether the target is an IList<T> so it can avoid iterating :
            public static TSource Single(this IEnumerable 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();



            UPDATE



            Checking the git blame output shows that the iteration optimization was applied back in 2016!



            The IList<> optimization was added 1 year ago, probably as part of the Core 2.1 optimizations






            share|improve this answer






















            • Indeed. You noticed the same as I did. :)
              – Patrick Hofman
              24 mins ago










            • Well this answers the question: It was actually a missed opportunity, which was corrected.
              – Matthew Watson
              23 mins ago










            • @MatthewWatson a lot of optimizations were added when 2.1 was introduced but Single.cs seems to go back to 2016
              – Panagiotis Kanavos
              22 mins ago












            up vote
            4
            down vote










            up vote
            4
            down vote









            The optimization was applied in .NET Core



            The code now is :



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



            Wherever possible, the code even checks whether the target is an IList<T> so it can avoid iterating :
            public static TSource Single(this IEnumerable 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();



            UPDATE



            Checking the git blame output shows that the iteration optimization was applied back in 2016!



            The IList<> optimization was added 1 year ago, probably as part of the Core 2.1 optimizations






            share|improve this answer














            The optimization was applied in .NET Core



            The code now is :



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



            Wherever possible, the code even checks whether the target is an IList<T> so it can avoid iterating :
            public static TSource Single(this IEnumerable 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();



            UPDATE



            Checking the git blame output shows that the iteration optimization was applied back in 2016!



            The IList<> optimization was added 1 year ago, probably as part of the Core 2.1 optimizations







            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited 20 mins ago

























            answered 25 mins ago









            Panagiotis Kanavos

            51.4k478106




            51.4k478106











            • Indeed. You noticed the same as I did. :)
              – Patrick Hofman
              24 mins ago










            • Well this answers the question: It was actually a missed opportunity, which was corrected.
              – Matthew Watson
              23 mins ago










            • @MatthewWatson a lot of optimizations were added when 2.1 was introduced but Single.cs seems to go back to 2016
              – Panagiotis Kanavos
              22 mins ago
















            • Indeed. You noticed the same as I did. :)
              – Patrick Hofman
              24 mins ago










            • Well this answers the question: It was actually a missed opportunity, which was corrected.
              – Matthew Watson
              23 mins ago










            • @MatthewWatson a lot of optimizations were added when 2.1 was introduced but Single.cs seems to go back to 2016
              – Panagiotis Kanavos
              22 mins ago















            Indeed. You noticed the same as I did. :)
            – Patrick Hofman
            24 mins ago




            Indeed. You noticed the same as I did. :)
            – Patrick Hofman
            24 mins ago












            Well this answers the question: It was actually a missed opportunity, which was corrected.
            – Matthew Watson
            23 mins ago




            Well this answers the question: It was actually a missed opportunity, which was corrected.
            – Matthew Watson
            23 mins ago












            @MatthewWatson a lot of optimizations were added when 2.1 was introduced but Single.cs seems to go back to 2016
            – Panagiotis Kanavos
            22 mins ago




            @MatthewWatson a lot of optimizations were added when 2.1 was introduced but Single.cs seems to go back to 2016
            – Panagiotis Kanavos
            22 mins ago

















             

            draft saved


            draft discarded















































             


            draft saved


            draft discarded














            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53042806%2fwhy-does-enumerable-single-iterate-all-elements-even-when-more-than-one-item%23new-answer', 'question_page');

            );

            Post as a guest













































































            Comments

            Popular posts from this blog

            What does second last employer means? [closed]

            List of Gilmore Girls characters

            Confectionery