Why does Enumerable.Single() iterate all elements, even when more than one item has already been found?
Clash Royale CLAN TAG#URR8PPP
up vote
11
down vote
favorite
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
 |Â
show 2 more comments
up vote
11
down vote
favorite
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
1
Optimizing failure paths is rarely worth doing.
– Damien_The_Unbeliever
32 mins ago
@Damien_The_Unbeliever In that case, why did they optimiseSingleOrDefault<TSource>(this IEnumerable<TSource> source)
to cast the source toIList
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 - theIList
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
 |Â
show 2 more comments
up vote
11
down vote
favorite
up vote
11
down vote
favorite
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
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
c# linq .net-4.0
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 optimiseSingleOrDefault<TSource>(this IEnumerable<TSource> source)
to cast the source toIList
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 - theIList
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
 |Â
show 2 more comments
1
Optimizing failure paths is rarely worth doing.
– Damien_The_Unbeliever
32 mins ago
@Damien_The_Unbeliever In that case, why did they optimiseSingleOrDefault<TSource>(this IEnumerable<TSource> source)
to cast the source toIList
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 - theIList
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
 |Â
show 2 more comments
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.
add a comment |Â
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
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
add a comment |Â
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.
add a comment |Â
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.
add a comment |Â
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.
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.
answered 27 mins ago


Patrick Hofman
123k18162216
123k18162216
add a comment |Â
add a comment |Â
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
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
add a comment |Â
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
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
add a comment |Â
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
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
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
add a comment |Â
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
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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 toIList
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