在我的代码中,我需要使用IEnumerable<>几次,导致ReSharper错误“可能的IEnumerable多重枚举”。
示例代码:
public List<object> Foo(IEnumerable<object> objects)
{
if (objects == null || !objects.Any())
throw new ArgumentException();
var firstObject = objects.First();
var list = DoSomeThing(firstObject);
var secondList = DoSomeThingElse(objects);
list.AddRange(secondList);
return list;
}
我可以将对象参数改为List,然后避免可能的多重枚举,但这样我就得不到我能处理的最高的对象。
我能做的另一件事是在方法的开头将IEnumerable转换为List:
public List<object> Foo(IEnumerable<object> objects)
{
var objectList = objects.ToList();
// ...
}
但这太尴尬了。
在这种情况下你会怎么做?
将IEnumerable作为参数的问题是,它告诉调用者“我希望枚举这个”。它不会告诉他们你希望列举多少次。
我可以将对象参数改为List,然后避免可能的多重枚举,但这样我就得不到我能处理的最高的对象。
夺取最高目标的目标是高尚的,但它给太多假设留下了空间。你真的希望有人将一个LINQ to SQL查询传递给这个方法,只是为了让你枚举它两次(每次可能得到不同的结果?)
这里语义缺失的是,调用者可能不会花时间阅读方法的细节,可能会假设您只迭代一次——因此他们会传递给您一个昂贵的对象。你的方法签名没有指明任何一种方法。
通过将方法签名更改为IList/ICollection,您至少可以让调用者更清楚地了解您的期望是什么,并且可以避免代价高昂的错误。
否则,大多数查看该方法的开发人员可能会认为您只迭代一次。如果获取IEnumerable是如此重要,您应该考虑在方法的开头执行. tolist()。
遗憾的是。net没有IEnumerable + Count + Indexer的接口,没有Add/Remove等方法,这是我认为可以解决这个问题的方法。
如果您的数据总是可重复的,也许就不用担心了。但是,你也可以展开它——如果传入的数据可能很大(例如,从磁盘/网络读取),这特别有用:
if(objects == null) throw new ArgumentException();
using(var iter = objects.GetEnumerator()) {
if(!iter.MoveNext()) throw new ArgumentException();
var firstObject = iter.Current;
var list = DoSomeThing(firstObject);
while(iter.MoveNext()) {
list.Add(DoSomeThingElse(iter.Current));
}
return list;
}
注意,我稍微改变了DoSomethingElse的语义,但这主要是为了显示展开的用法。例如,您可以重新包装迭代器。你也可以把它变成一个迭代器块,这样很好;然后就没有列表了——你会在得到它们的时候就返回它们,而不是添加到要返回的列表中。
.NET 6/ c# 10
. .除此之外,您可以尝试确定序列中的元素数量,而无需使用Enumerable强制枚举。TryGetNonEnumeratedCount(IEnumerable, Int32)方法。
如果source的计数可以在没有枚举的情况下确定,则此方法返回true;否则,假的。因此,您可以检查是否需要进一步实现。
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
IEnumerable<int> arrayOne = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var canGetCountDirectly = arrayOne.TryGetNonEnumeratedCount(out int theCount);
Console.WriteLine($"Count can be returned directly = {canGetCountDirectly}");
Console.WriteLine($"Count = {theCount}");
}
}
如果目的真的是为了防止多重枚举,那么Marc Gravell的答案是值得阅读的,但保持相同的语义,你可以简单地删除冗余的Any和First调用,并使用:
public List<object> Foo(IEnumerable<object> objects)
{
if (objects == null)
throw new ArgumentNullException("objects");
var first = objects.FirstOrDefault();
if (first == null)
throw new ArgumentException(
"Empty enumerable not supported.",
"objects");
var list = DoSomeThing(first);
var secondList = DoSomeThingElse(objects);
list.AddRange(secondList);
return list;
}
注意,这假设你IEnumerable不是泛型的,或者至少被限制为引用类型。