如何正确检查现有结果的 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
。
提前谢谢。
我可能会使用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();
}
}
将结果包装在此类型的实例中,它不会多次计算内部枚举。