在Resharper 5中,以下代码导致 list
的警告"Parameter can be declared with base type":
public void DoSomething(List<string> list)
{
if (list.Any())
{
// ...
}
foreach (var item in list)
{
// ...
}
}
在Resharper 6中,情况并非如此 . 但是,如果我将方法更改为以下内容,我仍然会收到警告:
public void DoSomething(List<string> list)
{
foreach (var item in list)
{
// ...
}
}
原因是,在此版本中,列表仅枚举一次,因此将其更改为 IEnumerable<string>
将不会自动引入另一个警告 . 现在,如果我手动更改第一个版本以使用 IEnumerable<string>
而不是 List<string>
,我将在方法正文中的两次出现 list
上收到警告("Possible multiple enumeration of IEnumerable"):
public void DoSomething(IEnumerable<string> list)
{
if (list.Any()) // <- here
{
// ...
}
foreach (var item in list) // <- and here
{
// ...
}
}
我理解,为什么,但我想知道,如何解决这个警告,假设,该方法实际上只需要 IEnumerable<T>
而不是 List<T>
,因为我只想枚举项目而我不想更改列表 .
在方法开头添加 list = list.ToList();
会使警告消失:
public void DoSomething(IEnumerable<string> list)
{
list = list.ToList();
if (list.Any())
{
// ...
}
foreach (var item in list)
{
// ...
}
}
我理解,为什么这会让警告消失,但它看起来有点像我的黑客......
任何建议,如何更好地解决该警告仍然使用方法签名中可能的最一般类型?
一个好的解决方案应该解决以下问题:
-
方法内部没有调用
ToList()
,因为它会对性能产生影响 -
不使用
ICollection<T>
或甚至更专业的接口/类,因为它们改变了从调用者看到的方法的语义 . -
在
IEnumerable<T>
上没有多次迭代,因此有多次或类似地访问数据库的风险 .
注意:我知道这不是一个Resharper问题,因此,我不想压制这个警告,但是因为警告是合法的,所以要解决根本原因 .
UPDATE: 请不要关心 Any
和 foreach
. 我不需要帮助合并这些语句只有一个可枚举的枚举 .
这个方法中的任何东西都可以枚举可枚举的多次!
13 回答
一般来说,你需要的是一些状态对象,你可以在其中推送项目(在foreach循环中),然后从中获得最终结果 .
可枚举的LINQ运算符的缺点是它们主动枚举源而不是接受被推送到它们的项,因此它们不符合您的要求 .
如果你是只需要1'000'000整数序列的最小值和最大值,这需要花费1000美元的处理器时间来检索,你最终写的是这样的:
实际上,您只是重新实现了Min()和Max()运算符的逻辑 . 当然这很容易,但它们只是任意复杂逻辑的例子,否则你可以用LINQish方式轻松表达 .
解决方案在昨天晚上散步时来到我身边:我们需要推......这是反应性的!所有心爱的运营商也存在于为推送范例构建的反应式版本中 . 它们可以随意链接到您需要的任何复杂程度,就像它们的可枚举对应物一样 .
所以min / max示例归结为:
这个类将为您提供一种方法,可以将第一个项目从枚举中分离出来,然后在枚举的其余部分使用IEnumerable,而不会给出双重枚举,从而避免了可能令人讨厌的性能损失 . 它的用法是这样的(其中T是你要枚举的任何类型):
这是 class 本身:
这确实预先假定IEnumerable至少有一个要启动的元素 . 如果您想要执行更多FirstOrDefault类型设置,则需要捕获否则将在构造函数中抛出的异常 .
您应该采取
IEnumerable<T>
并忽略"multiple iterations"警告 .此消息警告您,如果将懒惰的可枚举(例如迭代器或昂贵的LINQ查询)传递给您的方法,迭代器的某些部分将执行两次 .
没有完美的解决方案,根据情况选择一个 .
enumerable.ToList,只要不修改列表,首先尝试"enumerable as List"可以优化它
在IEnumerable上迭代两次,但为调用者清楚(记录它)
分为两种方法
列表以避免"as" / ToList的成本和双重枚举的潜在成本
对于可以在任何Enumerable上工作的公共方法,第一个解决方案(ToList)可能是最“正确的” .
您可以忽略Resharper问题,警告在一般情况下是合法的,但在您的具体情况下可能是错误的 . 特别是如果该方法用于内部使用并且您可以完全控制呼叫者 .
存在一种解决Resharper警告的一般解决方案:缺乏对IEnumerable的重复能力的保证,以及List基类(或可能昂贵的ToList()变通方法) .
创建一个专门的类,I.E“RepeatableEnumerable”,实现IEnumerable,并使用以下逻辑大纲实现“GetEnumerator()”:
从内部列表中收集到目前为止已收集的所有项目 .
如果包装的枚举器有更多的项目,
虽然包装的枚举器可以移动到下一个项目,
从内部枚举器中获取当前项 .
将当前项添加到内部列表 .
收益当前项目
将内部枚举器标记为不再有项目 .
添加扩展方法和适当的优化,其中包装参数已经可重复 . Resharper将不再标记以下代码上的指示警告:
通过一些工作,您还可以将RepeatableEnumerable转换为LazyList,这是IList的完整实现 . 但这超出了这个特定问题的范围 . :)
更新:注释中请求的代码实现 - 不确定为什么原始PDL不够,但无论如何,以下忠实地实现了我建议的算法(我自己的实现实现了完整的IList接口;这有点超出了范围我想在这里发布...... :))
我意识到这个问题很老,已经标记为已回答,但我很惊讶没有人建议手动迭代枚举器:
看起来比这里的其他答案简单得多 .
为什么不:
Update: 就个人而言,我不会让代码更好(并且可能会使情况变得更糟)来解决警告;然后错过警告点 .
例如:
UIMS * - 从根本上说,没有很好的解决方案 . IEnumerable <T>曾经是“代表一堆相同类型的东西的非常基本的东西,因此在方法sigs中使用它是正确的 . ”它现在也变成了“可能在幕后评估的东西,可能需要一段时间,所以现在你总是要担心这一点 . ”
这就好像IDictionary通过类型为Func <TKey,TValue>的LazyLoader属性突然扩展为支持延迟加载值 . 实际上,它很好,但不是那么整洁,不能添加到IDictionary,因为现在每次收到IDictionary我们都要担心 . 但那就是我们所处的位置 .
所以看起来“如果一个方法需要一个IEnumerable并且它可以避免两次,那么总是通过ToList()强制eval”是你能做的最好的事情 . Jetbrains的出色工作给了我们这个警告 .
*(除非我错过了某些东西......刚刚完成它但看起来很有用)
在接受方法中的可枚举时要小心 . 基类型的“警告”只是一个提示,枚举警告是一个真正的警告 .
但是,您的列表将被列举至少两次,因为您执行任何操作,然后是foreach . 如果添加
ToList()
,则枚举将被枚举三次 - 删除ToList() .我建议将基本类型的resharpers警告设置设置为提示 . 所以你仍然有一个提示(绿色下划线)和快速修改它(alt输入)和文件中没有“警告”的可能性 .
如果枚举IEnumerable是一个昂贵的操作,如从文件或数据库加载某些东西,或者如果你有一个计算值并使用yield return的方法,你应该小心 . 在这种情况下,首先执行
ToList()
或ToArray()
来加载/计算仅ONCE的所有数据 .你可以使用
ICollection<T>
(或IList<T>
) . 它不如List<T>
具体,但不会受到多枚举问题的影响 .在这种情况下,我仍倾向于使用
IEnumerable<T>
. 您还可以考虑重构代码以仅枚举一次 .使用IList作为参数类型而不是IEnumerable - IEnumerable与List具有不同的语义,而IList具有相同的语义
IEnumerable可能基于不可搜索的流,这就是您收到警告的原因
您只能迭代一次:
之前没有人说过(@Zebi) . Any()已经迭代尝试查找元素 . 如果你打电话给ToList(),它也会迭代,以创建一个列表 . 使用IEnumerable的最初想法只是迭代,其他任何东西都会激发迭代才能执行 . 您应该尝试在单个循环内完成所有操作 .
并在其中包含您的.Any()方法 .
如果你在方法中传递一个Action列表,那么一旦代码就会有一个更清晰的迭代