Why does Single() not return directly when more th

2019-06-14 23:16发布

问题:

This question already has an answer here:

  • Bad implementation of Enumerable.Single? 7 answers

I found (roughly) this code in the Enumerable.Single method while inspecting it with some decompiler:

foreach (TSource current in source)
{
    if (predicate(current))
    {
        result = current;
        num += 1L;
    }
}

if (num > 1L)
{
     throw Error.MoreThanOneMatch();
}

As you can see, it loops over all items before throwing. Why doesn't it break when num > 1?

回答1:

Agree, that it will be better from terms of performance (EDIT: if we are expecting more than one item matching our predicate, which we should not do):

foreach (TSource current in source)
{
    if (predicate(current))
    {
        result = current;
        num += 1L;

        if (num > 1L)
            throw Error.MoreThanOneMatch();
    }
}

if (num == 0L)
   throw Error.NoMatch();

return local;

Looks like they decided to make results analyzing more clear and separated it from enumerating source. But then I wonder why simple switch was not used:

switch((int)num)
{
   case 0: throw Error.NoMatch();
   case 1: return local;
   default:
       throw Error.MoreThanOneMatch();    
}

Regarding to performance issues - I think it's assumed that Single should be called when you are really expecting single result. Zero or more results is an exceptional path, which should not occur often (as any exception). So, it's more your program's logic error if source contain many items matching predicate.



回答2:

By Single is meant, exactly one, not none and also not more than one.
It enumerates all items, to ensure that it's only one.
It throws an exception, if there is none or more than one.
SingleOrDefault instead throws if there are more, but returns default(T)/null if there is none.

What you're looking for is FirstOrDefault that breaks the enumeration, if it found the first one matching the predicate. First instead throws if there is none, and also breaks (directly returns from) it's foreach, if it found the first one.

FirstOrDefault's source

foreach (TSource current in source)
{
    if (predicate(current))
    {
        return current;
    }
}
return default(TSource);

Whereas First's source is instead of return default

throw Error.NoMatch();