如何正确检查IEnumerable的现有结果

时间:2012-02-13 09:38:39

标签: c# linq

检查集合是否包含项目的最佳做法是什么?

以下是我所拥有的一个例子:

var terminalsToSync = TerminalAction.GetAllTerminals();

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

GetAllTerminals()方法将执行存储过程,如果我们返回结果,(Any()true),SyncTerminals()将遍历元素;从而再次枚举它并第二次执行存储过程。

避免这种情况的最佳方法是什么?

我想要一个可以在其他情况下使用的好解决方案;可能没有将其转换为List

提前致谢。

8 个答案:

答案 0 :(得分:5)

我可能会使用ToArray来电,然后检查Length;无论如何你要列举所有的结果,为什么不早点呢?但是,既然你已经说过要避免早期实现可枚举......

我猜测SyncTerminals有一个foreach,在这种情况下你可以这样写:

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

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

好的,在第一个循环之后有一个冗余的if,但我猜测额外几个CPU周期的成本并不重要。

同样,您可以使用旧方法进行迭代并使用do...while循环和GetEnumerator;将第一次迭代从循环中取出;这样就没有浪费的操作:

var enumerator = terminalsToSync.GetEnumerator();
if(enumerator.MoveNext())
{
  do
  {
    //sync enumerator.Current
  } while(enumerator.MoveNext())
}
else
  GatewayLogAction.WriteLogInfo(Messages.NoTerminalsForSync);

答案 1 :(得分:3)

我个人不会在这里使用任何一个,如果集合是空的,foreach将不会遍历任何项目,所以我会这样做。但是我建议你检查一下。

如果你想要预先枚举集合使用.ToArray()例如只会枚举一次:

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

if(terminalsToSync.Any())
    SyncTerminals(terminalsToSync);

答案 2 :(得分:3)

这个怎么样,仍然延迟执行,但是一旦执行就缓冲它:

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

使用:

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

答案 3 :(得分:2)

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

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

答案 4 :(得分:1)

.Length.Count更快,因为它不需要通过Any()

所需的GetEnumerator()/ MoveNext()/ Dispose()

答案 5 :(得分:1)

这是解决此问题的另一种方法:

int count = SyncTerminals(terminalsToSync);
if(count == 0)  GatewayLogAction.WriteLogInfo(Messages.NoTerminalsForSync);

您要将SyncTerminals更改为:

int count = 0;
foreach(var obj in terminalsToSync) {
    count++;
    // some code
}
return count;

美好而简单。

答案 6 :(得分:1)

此处的所有缓存解决方案都是在检索第一个项目时缓存所有项目。如果你缓存每个单独的项目列表的项目被迭代,它真的很懒。

在这个例子中可以看出差异:

public class LazyListTest
{
    private int _count = 0;

    public void Test()
    {
        var numbers = Enumerable.Range(1, 40);
        var numbersQuery = numbers.Select(GetElement).ToLazyList(); // Cache lazy
        var total = numbersQuery.Take(3)
            .Concat(numbersQuery.Take(10))
            .Concat(numbersQuery.Take(3))
            .Sum();
        Console.WriteLine(_count);
    }

    private int GetElement(int value)
    {
        _count++;
        // Some slow stuff here...
        return value * 100;
    }
}

如果你运行Test()方法,_coun t只有10.没有缓存,它将是16,而.ToList()则是40!

implementation of LazyList can be found here的一个例子。

答案 7 :(得分:0)

如果你看到两个过程调用来评估任何GetAllTerminals()返回,这意味着过程的结果没有被缓存。在不知道您正在使用什么数据访问策略的情况下,这很难以一般方式解决。

正如您所提到的,最简单的解决方案是在执行任何其他操作之前复制调用的结果。如果你愿意,你可以巧妙地将这种行为包装在IEnumerable<T>中,它只执行一次内部可枚举调用:

public class CachedEnumerable<T> : IEnumerable<T>
{
    public CachedEnumerable<T>(IEnumerable<T> enumerable)
    {
        result = new Lazy<List<T>>(() => enumerable.ToList());
    }

    private Lazy<List<T>> result;

    public IEnumerator<T> GetEnumerator()
    {
        return this.result.Value.GetEnumerator();
    }

    System.Collections.IEnumerable GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

将结果包装在此类型的实例中,并且不会多次评估内部可枚举。