为了账号安全,请及时绑定邮箱和手机立即绑定

处理可能多次枚举IEnumerable的警告

处理可能多次枚举IEnumerable的警告

子衿沉夜 2019-08-30 15:08:27
在我的代码中需要使用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;}我可以更改objects参数List,然后避免可能的多次枚举,但后来我没有得到我能处理的最高对象。我可以做的另一件事是将转换IEnumerable到List在方法的开头: public List<object> Foo(IEnumerable<object> objects) {    var objectList = objects.ToList();    // ... }但这只是尴尬。在这种情况下你会做什么?
查看完整描述

3 回答

?
心有法竹

TA贡献1866条经验 获得超5个赞

IEnumerable作为参数的问题在于它告诉呼叫者“我希望枚举这个”。它没有告诉他们你想要枚举多少次。


我可以将objects参数更改为List,然后避免可能的多次枚举,但后来我没有得到我能处理的最高对象。


采取最高目标的目标是高尚的,但它为太多的假设留下了空间。你真的希望有人将LINQ to SQL查询传递给这个方法,只为你枚举它两次(每次得到可能不同的结果吗?)


这里缺少的语义是,调用者可能没有花时间阅读方法的细节,可能假设您只迭代一次 - 因此他们会传递给您一个昂贵的对象。您的方法签名不表示任何一种方式。


通过将方法签名更改为IList/ ICollection,您至少可以使调用者更清楚您的期望是什么,并且可以避免代价高昂的错误。


否则,大多数查看该方法的开发人员可能会假设您只迭代一次。如果采取一个IEnumerable非常重要的,你应该考虑.ToList()在方法的开头做。


遗憾的是.NET没有IEnumerable + Count + Indexer的接口,没有Add / Remove等方法,这是我怀疑会解决这个问题的方法。


查看完整回答
反对 回复 2019-08-30
?
翻翻过去那场雪

TA贡献2065条经验 获得超13个赞

如果您的数据总是可重复的,也许不用担心。但是,您也可以将其展开 - 如果传入的数据很大(例如,从磁盘/网络读取),这尤其有用:


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的语义,但这主要是为了显示展开的用法。例如,您可以重新包装迭代器。你也可以把它变成一个迭代器块,这可能很好; 然后没有list- 你会yield return得到它们的项目,而不是添加到要返回的列表。


查看完整回答
反对 回复 2019-08-30
?
偶然的你

TA贡献1841条经验 获得超3个赞

在方法签名中使用IReadOnlyCollection<T>或IReadOnlyList<T>代替IEnumerable<T>,可以明确表示您可能需要在迭代之前检查计数,或者由于某些其他原因而多次迭代。


但是,如果您尝试重构代码以使用接口,它们会产生很大的缺点,例如使其更易于测试并且对动态代理更友好。关键点在于,IList<T>它不会继承IReadOnlyList<T>,也类似于其他集合及其各自的只读接口。(简而言之,这是因为.NET 4.5希望保持ABI与早期版本的兼容性。但他们甚至没有机会在.NET核心中改变它。)


这意味着如果你IList<T>从程序的某些部分得到一个并希望将它传递给另一个期望的部分IReadOnlyList<T>,你就不能!但是你可以传递IList<T>一个IEnumerable<T>。


最后,IEnumerable<T>是所有.NET集合支持的唯一只读接口,包括所有集合接口。任何其他选择都会回来咬你,因为你意识到你把自己锁定在某些架构选择之外。所以我认为在函数签名中使用它是正确的类型来表示你只需要一个只读集合。


(请注意,IReadOnlyList<T> ToReadOnly<T>(this IList<T> list)如果底层类型支持两个接口,您总是可以编写一个简单强制转换的扩展方法,但是您必须在重构时手动添加它,而且IEnumerable<T>总是兼容的。)


一如既往,这不是绝对的,如果你正在编写数据库密集的代码,意外的多次枚举将是一场灾难,你可能更喜欢不同的权衡。


查看完整回答
反对 回复 2019-08-30
  • 3 回答
  • 0 关注
  • 385 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信