关于c#:在数组上使用IEnumerable有什么好处?

What are the advantages of using IEnumerable over an Array?

在问了我关于code review.stackexchange的问题之后,我被建议使用以下代码片段。我注意到在传输过程中,字符串[]行被设置为IEnumerable。

在研究了一下IEnumerable函数之后,我没有发现任何建议性能改进的地方。这只是为了可读性吗?或者使用IEnumerable而不是数组实际上有性能差异或一般优势吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void ProcessSingleItem(String fileName, String oldId, String newId)
{
    string[] lines = File.ReadAllLines(fileName);

    File.WriteAllText(fileName, ProcessLines(lines, oldId, newId));
}  

private String ProcessLines(IEnumerable<String> lines, String oldId, String newId)
{
    StringBuilder sb = new StringBuilder(2048);
    foreach (String line in lines)
    {
        sb.AppendLine(line.Replace(oldId, newId));
    }
    return sb.ToString();
}


到目前为止,所有的答案都说,在你所接受的方面更为普遍,会使你的助手方法更有用。这是正确的。然而,也有其他的考虑。

  • pro:取一个序列而不是一个数组来与调用您的代码的开发人员通信,"我不会改变您传递给我的对象"。当我调用一个接受数组的方法时,我怎么知道它不会改变数组?

  • con:采用一个更通用的类型意味着您的方法对于该更通用类型的任何实例都必须是正确的。你怎么知道是正确的?测试。因此,采用更通用的类型可能意味着更大的测试负担。如果采用数组,那么只有少数情况:空数组、空数组、协变数组等等。如果采用序列,则要测试的案例数会更大。

  • 你提到了表演。记住,基于感觉而不是经验数据做出微观决策是获得良好绩效的糟糕方法。相反,设置一个性能目标,根据该目标测量您的进度,并使用分析器来查找最慢的事情;首先攻击它。数组上的foreach循环编译为等效的for循环;在IEnumerable上,代码更复杂,可能慢几微秒。您的应用程序在市场上的成功或失败将由这些微秒决定吗?如果是,则仔细测量性能。如果没有,按照您喜欢的方式编写代码,并且如果您引入了一个回归,使您不再满足您的目标,您的自动化测试将告诉您关于它的信息。您正在运行自动性能测试,对吗?如果你如此关心表演,你应该是。


数组是IEnumerable的实现。

方法中从lines参数使用的唯一成员是在IEnumerable中定义的成员。因此,您可以选择IEnumerable作为参数类型,并且在接受的方面最不挑剔,允许将任何IEnumerable实现作为参数提供。

考虑这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
ProcessLines(GetItems(), ...);

public IEnumerable<string> GetItems()
{
    yield return"ItemAlwaysGetsIncluded";

    if (!once_in_blue_moon)
    {
        yield break;
    }

    yield return"ItemIncludedOnceInABlueMoon";
}

很难判断性能的影响是什么,因为IEnumerable可以是任何东西。


看看德米特定律。您希望参数尽可能通用,以便在更多情况下使用。

现在传递任何实现IEnumerable的集合,并且不仅限于数组。

在这种情况下,它更多的是一个设计问题,而不是性能问题。返回值是另一种情况,尽管在这里肯定有一些性能提升。

在本例中,您只是迭代,所以您只需要IEnumerable


IEnumerable只提供最小的"Iterable"功能。你可以遍历序列,但就是这样。这有缺点——例如,使用IEnumerable计算元素或获取第n个元素效率很低——但它也有优点——例如,IEnumerable可以是一个无止境的序列,就像素数序列一样。

数组是一个固定大小的集合,具有随机访问权限(即可以对其进行索引)


在上面的代码中,它与实现数组没有任何区别,如果使用

1
2
3
4
5
6
7
8
System.IO.StreamReader file = new System.IO.StreamReader("c:\\test.txt");

while((line = file.ReadLine()) != null)
{
   yield return line;
}

file.Close();

在这里因此,在上面的代码中,我们将始终只有一行内存,这有助于以只有几个字节的内存消耗来读取更大的文件大小。

此外,一般规律是,当将输入参数传递给函数类型时,应该被广泛接受,因为所有集合和基于集合的接口都实现了IEnumerable,所以最好将参数类型设置为IEnumerable,以便可以传递列表或任何其他集合类型。