Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly check IEnumerable for existing results

Tags:

c#

linq

What's the best practice to check if a collection has items?

Here's an example of what I have:

var terminalsToSync = TerminalAction.GetAllTerminals();

if(terminalsToSync.Any())
    SyncTerminals(terminalsToSync);
else
    GatewayLogAction.WriteLogInfo(Messages.NoTerminalsForSync);

The GetAllTerminals() method will execute a stored procedure and, if we return a result, (Any() is true), SyncTerminals() will loop through the elements; thus enumerating it again and executing the stored procedure for the second time.

What's the best way to avoid this?

I'd like a good solution that can be used in other cases too; possibly without converting it to List.

Thanks in advance.

like image 200
Fedor Hajdu Avatar asked Feb 13 '12 09:02

Fedor Hajdu


2 Answers

I would probably use a ToArray call, and then check Length; you're going to enumerate all the results anyway so why not do it early? However, since you've said you want to avoid early realisation of the enumerable...

I'm guessing that SyncTerminals has a foreach, in which case you can write it something like this:

bool any = false;
foreach(var terminal in terminalsToSync)
{
  if(!any)any = true;
  //....
}

if(!any)
  GatewayLogAction.WriteLogInfo(Messages.NoTerminalsForSync);

Okay, there's a redundant if after the first loop, but I'm guessing the cost of an extra few CPU cycles isn't going to matter much.

Equally, you could do the iteration the old way and use a do...while loop and GetEnumerator; taking the first iteration out of the loop; that way there are literally no wasted operations:

var enumerator = terminalsToSync.GetEnumerator();
if(enumerator.MoveNext())
{
  do
  {
    //sync enumerator.Current
  } while(enumerator.MoveNext())
}
else
  GatewayLogAction.WriteLogInfo(Messages.NoTerminalsForSync);
like image 161
Andras Zoltan Avatar answered Oct 04 '22 04:10

Andras Zoltan


How about this, which still defers execution, but buffers it once executed:

var terminalsToSync = TerminalAction.GetAllTerminals().Lazily();

with:

public static class LazyEnumerable {
    public static IEnumerable<T> Lazily<T>(this IEnumerable<T> source) {
        if (source is LazyWrapper<T>) return source;
        return new LazyWrapper<T>(source);
    }
    class LazyWrapper<T> : IEnumerable<T> {
        private IEnumerable<T> source;
        private bool executed;
        public LazyWrapper(IEnumerable<T> source) {
            if (source == null) throw new ArgumentNullException("source");
            this.source = source;
        }
        IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
        public IEnumerator<T> GetEnumerator() {
            if (!executed) {
                executed = true;
                source = source.ToList();
            }
            return source.GetEnumerator();
        }
    }
}
like image 28
Marc Gravell Avatar answered Oct 04 '22 04:10

Marc Gravell