1
votes

I'm getting a Possible multiple enumeration of IEnumerable with Resharper and I'm trying to find out if it's really an issue or not. This is my method:

public IEnumerable<Contact> GetContacts(IContactManager contactManager, string query)
{
    IEnumerable<Contact> contacts = contactManager.GetContacts(query);
    if (contacts.Any()) return contacts; // Get the warning on this line
    // Do some other stuff
    return new[] {
        new Contact { Name = "Example" }
    }
}

Should be obvious, but I'm doing a search for Contact and if the search returns no results I'm returning an array of default values. The consumer should just receive a list which can be enumerated, not modified.

Where is the "multiple enumeration" here? And if there is indeed one, is this not the best type to use in the situation?

2

2 Answers

5
votes

The multiple enumeration potential is you calling Any, which will cause the first enumeration, and then a potential second enumeration by the caller of that method.

In this instance, I'd guess it is mostly guaranteed that two enumerations will occur at least.

The warning exists because an IEnumerable can disguise something expensive such as a database call (most likely an IQueryable), and as IEnumerable doesn't have caching as part of it's contract, it will re-enumerate the source fresh. This can lead to performance issues later on (we have been stung by this a surprising amount and we don't even use IQueryable, ours was on domain model traversal).

That said, it is still only a warning, and if you are aware of the potential expense of calling an enumerable over potentially slow source multiple times then you can suppress it.

The standard answer to caching the results is ToList or ToArray.

Although I do remember making an IRepeatable version of IEnumerable once that internally cached as it went along. That was lost in the depths of my gratuitous code library :-)

3
votes

Enumerable.Any executes the query to check whether or not the sequnce contains elements. You could use DefaultIfEmpty to provide a different default value if there are no elements:

public IEnumerable<Contact> GetContacts(IContactManager contactManager, string query)
{
    IEnumerable<Contact> contacts = contactManager.GetContacts(query)
        .DefaultIfEmpty(new Contact { Name = "Example" });
    return contacts;
}

Note that this overload is not supported in LINQ-To-SQL.