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

时间:2015-12-09 12:16:30

标签: c#

在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方法将被调用两次,但它被调用一次。

我错过了什么?

4 个答案:

答案 0 :(得分:6)

它只被调用一次,因为Test2()实际上返回string [],这也是IEnumerable<string>.string []数组仍由items引用,因此每次使用items时,您只需重新使用该数组。

您期望的案例是Test2()的实施,iterator block

private static IEnumerable<string> Test2()
{
    string[] array1 = { "1", "2", "3" };

    foreach (var str in array1) 
    {
        yield return str; 
    }
}

答案 1 :(得分:5)

看一下这个例子:

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();

将立即评估可枚举并将其放入列表中。在这种情况下,工作只需一次

答案 2 :(得分:0)

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

为了避免您首先将数据集转换为有序集合:例如:在你的items变量上调用.ToArray()或.ToList()。

答案 3 :(得分:0)

正如许多人所指出的,这一警告的目的是指出昂贵的行动可能不止一次发生。发生这种情况是因为ReSharper看到你的方法返回一个可能导致延迟评估的IEnumerable,如果你使用yield返回或大多数LINQ方法。

当ReSharper可以确定您正在迭代的东西是一个集合时,它会停止关于多次评估的警告。您可以通过两种方式向ReSharper提供该信息。

  1. 将Test2的返回类型更改为IList<string>
  2. 在第一次之前 foreach添加System.Diagnostics.Debug.Assert(items is IList<string>);
  3. 如果对返回的ToList()使用IEnumerable<string>,ReSharper也会知道你正在迭代一个集合,但你也会创建一个不必要的临时列表(你已经有了一个数组),付费构建新列表的时间和内存成本。