检查集合是否包含项目的最佳做法是什么?
以下是我所拥有的一个例子:
var terminalsToSync = TerminalAction.GetAllTerminals();
if(terminalsToSync.Any())
SyncTerminals(terminalsToSync);
else
GatewayLogAction.WriteLogInfo(Messages.NoTerminalsForSync);
GetAllTerminals()
方法将执行存储过程,如果我们返回结果,(Any()
为true
),SyncTerminals()
将遍历元素;从而再次枚举它并第二次执行存储过程。
避免这种情况的最佳方法是什么?
我想要一个可以在其他情况下使用的好解决方案;可能没有将其转换为List
。
提前致谢。
答案 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()
答案 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!
答案 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();
}
}
将结果包装在此类型的实例中,并且不会多次评估内部可枚举。