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

本文关键字:结果 IEnumerable 何正确 检查 | 更新日期: 2023-09-27 18:24:22

检查集合是否有项目的最佳实践是什么?

这是我拥有的示例:

var terminalsToSync = TerminalAction.GetAllTerminals();
if(terminalsToSync.Any())
    SyncTerminals(terminalsToSync);
else
    GatewayLogAction.WriteLogInfo(Messages.NoTerminalsForSync);

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

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

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

提前谢谢。

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

我可能会使用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);

怎么样,它仍然延迟执行,但在执行后缓冲它:

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

就个人而言,我不会在这里使用 any,因为如果集合为空,则每个都不会循环访问任何项目,所以我会这样做。但是,我建议您检查空值。

如果确实要预先枚举集合,请使用 .ToArray(( eg 只会枚举一次:

var terminalsToSync = TerminalAction.GetAllTerminals().ToArray();
if(terminalsToSync.Any())
    SyncTerminals(terminalsToSync);
var terminalsToSync = TerminalAction.GetAllTerminals().ToList();
if(terminalsToSync.Any())
    SyncTerminals(terminalsToSync);
else
    GatewayLogAction.WriteLogInfo(Messages.NoTerminalsForSync);

.Length.Count更快,因为它不需要通过Any((所需的GetEnumerator((/MoveNext((/Dispose((

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

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;

很好,很简单。

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

在此示例中可以看出差异:

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() 方法,则 t _coun仅为 10。如果不缓存,它将是 16 和 .ToList(( 这将是 40!

LazyList的实现示例可以在这里找到。

如果您看到两个过程调用来评估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();
    }
}

将结果包装在此类型的实例中,它不会多次计算内部枚举。