如何证明返回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方法会被调用两次,但它只被调用了一次。

我错过了什么?

如何证明返回IEnumerable的方法已经被调用了两次

它只调用一次,因为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提供这些信息。

  1. 将Test2的返回类型更改为IList<string>
  2. 第一次之前foreachSystem.Diagnostics.Debug.Assert(items is IList<string>);

如果在返回的IEnumerable<string>上使用ToList(),ReSharper也会知道您正在对集合进行迭代,但您也会创建一个不必要的临时列表(您已经有了一个数组),从而为构建新列表付出时间和内存成本。

IEnumerable<T>是一个接口,它有一个枚举器,每次您想要访问数据集合时都会调用它(foreach循环)。Resharper警告您,如果您的数据没有排序,并且您在数据集上多次调用此枚举器,则运行时可能需要多次遍历您的集合,这可能会增加负载并降低执行时间。

为了避免这种情况,可以先将数据集强制转换为有序集合:例如,对items变量调用.ToArray()或.ToList()。