如何证明返回IEnumerable的方法已经被调用了两次
本文关键字:调用 两次 何证明 证明 返回 IEnumerable 方法 | 更新日期: 2023-09-27 18:26:16
在Visual Studio中,ReSharper对以下代码发出警告:"可能存在IEnumerable的多重枚举":
static void Main(string[] args)
{
IEnumerable<string> items = Test2();
foreach (var item in items)
{
Console.WriteLine(item);
}
var newitems = new StringBuilder();
foreach (var item in items)
{
newitems.Append(item);
}
}
private static IEnumerable<string> Test2()
{
string[] array1 = { "1", "2", "3" };
return array1;
}
我预计Test2方法会被调用两次,但它只被调用了一次。
我错过了什么?
它只调用一次,因为Test2()
实际上返回string []
,它也是IEnumerable<string>.
这个string []
数组仍然由items
引用,所以每次使用items
时,只需重用该数组。
您期望的案例是Test2()
的一个实现,它带有迭代器块:
private static IEnumerable<string> Test2()
{
string[] array1 = { "1", "2", "3" };
foreach (var str in array1)
{
yield return str;
}
}
看看这个例子:
void Main()
{
IEnumerable<int> items = Test2();
foreach (var item in items)
{
Console.WriteLine(item);
}
var newitems = new StringBuilder();
foreach (var item in items)
{
newitems.Append(item);
}
}
IEnumerable<int> Test2()
{
Console.WriteLine("Test2 called");
return GetEnum();
}
IEnumerable<int> GetEnum()
{
for(var i = 0; i < 5; i ++)
{
Console.WriteLine("Doing work...");
Thread.Sleep(50); //Download some information from a website, or from a database
yield return i;
}
}
假设return GetEnum();
是return new int[] { 1, 2, 3 }
现在,对于数组,多次迭代它们并不一定是坏事。在你的情况下,你可以在一个循环中完成这项工作,但这不是resharper警告你的原因。它警告您,因为Test2()
可能会在每次迭代时返回一个确实有效的惰性枚举值。
如果你运行上面的代码,你会得到这样的输出:
Test2 called
Doing work...
0
Doing work...
1
Doing work...
2
Doing work...
3
Doing work...
4
Doing work...
Doing work...
Doing work...
Doing work...
Doing work...
注意,Test2
本身只被调用一次,但可枚举对象被迭代两次(工作完成两次!)。
你可以通过写以下内容来避免这种情况:
var items = Test2().ToList();
它将立即评估可枚举项并将其放入列表中。在这种情况下,工作只执行一次。
正如许多人所指出的,此警告的目的是指出昂贵的操作可能不止一次发生。发生这种情况是因为ReSharper看到您的方法返回IEnumerable,如果您使用yield返回或大多数LINQ方法,这可能会导致延迟求值。
当ReSharper可以确定您正在迭代的东西是一个集合时,它将停止警告多次求值。您可以通过两种方式向ReSharper提供这些信息。
- 将Test2的返回类型更改为
IList<string>
- 第一次之前
foreach
加System.Diagnostics.Debug.Assert(items is IList<string>);
如果在返回的IEnumerable<string>
上使用ToList()
,ReSharper也会知道您正在对集合进行迭代,但您也会创建一个不必要的临时列表(您已经有了一个数组),从而为构建新列表付出时间和内存成本。
IEnumerable<T>
是一个接口,它有一个枚举器,每次您想要访问数据集合时都会调用它(foreach循环)。Resharper警告您,如果您的数据没有排序,并且您在数据集上多次调用此枚举器,则运行时可能需要多次遍历您的集合,这可能会增加负载并降低执行时间。
为了避免这种情况,可以先将数据集强制转换为有序集合:例如,对items变量调用.ToArray()或.ToList()。