“Possible multiple enumeration of IEnumerable”与“参数可以声明为基本类型”的区别。

15

在 Resharper 5 中,以下代码会导致对 list 出现 "参数可以使用基础类型进行声明" 的警告:

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 的两个出现都会收到警告("可能多次枚举 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)
    {
        // ...
    }
}
我明白这样做可以消除警告,但对我来说看起来有点像一个hack... 有什么建议可以更好地解决警告,并在方法签名中仍然使用最通用的类型吗?要解决以下所有问题才能得到一个好的解决方案:
  1. 不在方法内调用 ToList(),因为它会影响性能
  2. 不使用 ICollection<T> 或更专业化的接口/类,因为它们会改变从调用者看到的方法语义。
  3. 不要多次迭代 IEnumerable<T>,以避免多次访问数据库或类似的情况。
注意:我知道这不是Resharper的问题,因此,我不想压制此警告,而是要解决底层原因,因为该警告是合法的。
更新: 请不要关心 Anyforeach。 我不需要帮助将这些语句合并为只枚举可枚举对象的一个语句。 实际上,在此方法中,任何多次枚举可枚举对象的行为都可能导致问题!
13个回答

0

以前没有人说过的事情(@Zebi)。Any()已经在尝试查找元素时进行了迭代。如果调用ToList(),它也会进行迭代,以创建一个列表。使用IEnumerable的最初想法只是为了迭代,任何其他操作都会引发迭代以执行。你应该尝试在单个循环中完成所有操作。

并在其中包含你的.Any()方法。

如果在你的方法中传递一个Action列表,你将得到一个更简洁的迭代一次的代码。

public void DoSomething(IEnumerable<string> list, params Action<string>[] actions)
{
    foreach (var item in list)
    {
        for(int i =0; i < actions.Count; i++)
        {
           actions[i](item);
        }
    }
}

我并不是在寻找针对我的特定样本的解决方案。请查看我的问题更新(末尾加粗部分)... - Daniel Hilgarth

0
你可以使用 ICollection<T>(或者 IList<T>)。它比 List<T> 更为通用,并且不会出现多次枚举的问题。
但在这种情况下,我更倾向于使用 IEnumerable<T>。您也可以考虑重构代码,只枚举一次。

1
这不是一个好主意。两者都允许方法更改内容。调用者无法确定其可枚举对象的内容是否已更改。然而,使用IEnumerable<T>作为参数类型可以清楚地表明该方法不想更改内容... - Daniel Hilgarth

0
使用 IList 作为参数类型,而不是 IEnumerable - IEnumerable 与 List 具有不同的语义,而 IList 则相同。
IEnumerable 可以基于不可寻址的流,这就是为什么会收到警告的原因。

正是这个原因,我不想要 ListIList。语义是错误的。 - Daniel Hilgarth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接