何时使用T [],List <t>,IEnumerable <t>?</t> </t>

时间:2010-08-04 23:14:37

标签: c# ienumerable idioms

我经常发现自己在做类似的事情:

string[] things = arrayReturningMethod();
int index = things.ToList<string>.FindIndex((s) => s.Equals("FOO"));
//do something with index
return things.Distinct(); //which returns an IEnumerable<string>

我发现所有这些类型/接口的混合有点令人困惑,它发现了我潜在的性能问题触角(我忽略了它,直到被证明是正确的,当然)。

这是一个惯用且恰当的C#还是有更好的替代方法可以避免来回使用来访问使用数据的正确方法?

编辑: 问题实际上是双重的:

  • 何时直接使用IEnumerable接口或数组或列表(或任何其他IEnumerable实现类型)是正确的(接受参数时)?

  • 你是否可以在IEnumerables(实现未知)和列表以及IEnumerables和数组以及数组和列表之间自由移动,或者是非惯用的(有更好的方法)/非高性能(通常不相关,但可能)在某些情况下)/只是丑陋(不可维护,不可读)?

7 个答案:

答案 0 :(得分:7)

一个好的经验法则是始终使用IEnumerable(在声明变量/方法参数/方法返回类型/属性/等时),除非你有充分的理由不这样做。到目前为止,与其他(特别是扩展)方法的类型最兼容。

答案 1 :(得分:7)

关于表现......

  • 从List转换为T []涉及将原始列表中的所有数据复制到新分配的数组中。
  • 从T []转换为List还涉及将原始列表中的所有数据复制到新分配的列表。
  • 从List或T []转换为IEnumerable涉及转换,这是几个CPU周期。
  • 从IEnumerable转换为List涉及向上转换,这也是一些CPU周期。
  • 从IEnumerable转换为T []也涉及向上转换。
  • 你不能将IEnumerable转换为T []或List,除非它分别是T []或List。您可以使用ToArray或ToList函数,但这些函数也会导致复制。
  • 在T []中按从头到尾的顺序访问所有值将在一个简单的循环中进行优化,以使用直接的指针算法 - 这使得它们中最快的一个。
  • 在List中从头到尾按顺序访问所有值包括检查每次迭代,以确保您没有访问数组边界之外的值,然后实际访问数组值。
  • 访问IEnumerable中的所有值包括创建一个枚举器对象,调用Next()函数来增加索引指针,然后调用Current属性,该属性为您提供实际值并将其添加到您在中指定的变量中你的foreach声明。一般来说,这并不像听起来那么糟糕。
  • 访问IEnumerable中的任意值包括从头开始并根据需要多次调用Next()来获取该值。一般来说,这听起来很糟糕。

关于成语......

通常,IEnumerable对公共属性,函数参数以及返回值通常都很有用 - 并且只有当您知道要按顺序使用值时才会这样。

例如,如果你有一个函数PrintValues,如果它被写为PrintValues(List&lt; T&gt;值),它只能处理List值,所以用户首先必须转换,例如他们正在使用T []。同样,如果函数是PrintValues(T []值)。但如果它是PrintValues(IEnumerable&lt; T&gt;值),它将能够处理Lists,T [],堆栈,哈希表,字典,字符串,集合等 - 任何实现IEnumerable的集合,几乎每个集合。

关于内部使用......

  • 仅在您不确定需要包含多少项目时才使用列表。
  • 如果您知道需要有多少项,请使用T [],但需要以任意顺序访问这些值。
  • 坚持使用IEnumerable,如果你已经给出了它,你只需要按顺序使用它。许多函数都会返回IEnumerables。如果确实需要以任意顺序访问IEnumerable中的值,请使用ToArray()。

另外,请注意,转换与使用ToArray()或ToList()不同 - 后者涉及复制值,如果您有很多元素,这确实是性能和内存命中。前者只是说“狗是动物,所以像任何动物一样,它可以吃”(垂头丧气)或“这种动物恰好是一只狗,所以它可以吠叫”(向上)。同样,All Lists和T []是IEnumerables,但只有一些IEnumerables是Lists或T [] s。

答案 2 :(得分:3)

嗯,你有两个苹果和橙子,你正在比较。

两个苹果是数组和List。

  • C#中的数组是一个内置垃圾收集的C风格数组。使用它们的好处是它们的开销很小,假设你不需要移动东西。糟糕的是,当你添加内容,删除内容以及以其他方式更改数组时,它们并不是那么有效,因为内存会被随机移动。

  • List是C#样式的动态数组(类似于C ++中的vector&lt;&gt;类)。有更多的开销,但是当你需要大量移动时它们会更有效率,因为它们不会试图保持内存使用的连续性。

我能给出的最好的比较是说数组是列表,因为字符串是StringBuilders。

橙色是'IEnumerable'。这不是数据类型,而是接口。当一个类实现IEnumerable接口时,它允许该对象在foreach()循环中使用。

当您返回列表时(就像您在示例中所做的那样),您没有列表转换为IEnumerable。列表已经是 IEnumerable对象。

编辑:何时在两者之间进行转换:

这取决于应用程序。使用List无法完成的数组可以做的很少,所以我通常会推荐List。可能最好的做法是做出一个你要使用其中一个的设计决定,这样你就不必在两者之间切换。如果您依赖外部库,请将其抽象出来以保持一致的使用。

希望这能清除一点雾。

答案 3 :(得分:1)

在我看来,问题在于你没有费心学习如何搜索阵列。提示:Array.IndexOfArray.BinarySearch取决于数组是否已排序。

你是正确的,转换到列表是一个坏主意:它浪费空间和时间,使代码可读性降低。另外,盲目向上转换为IEnumerable会减慢事情的速度并完全阻止使用某些算法(例如二进制搜索)。

答案 4 :(得分:0)

如果可以避免,我会尽量避免在数据类型之间快速跳转。

必须出现的情况是,与您描述的情况类似的每种情况都是完全不同的,以防止关于改变您的类型的教条规则;但是,通常最好选择一种数据结构,尽可能提供所需的接口,而不必将元素不必要地复制到新的数据结构中。

答案 5 :(得分:0)

何时使用?

我建议返回最具体的类型,并采用最灵活的类型。

像这样:

public int[] DoSomething(IEnumerable<int> inputs)
{
    //...
}

public List<int> DoSomethingElse(IList<int> inputs)
{
    //...
}

这样你就可以在List< T >上调用方法,除了将它作为IEnumerable处理之外。在输入上,使用尽可能灵活,因此您不要指定您的方法的用户要创建哪种类型的集合。

答案 6 :(得分:-2)

在实际出现性能问题之前,你应该忽略“性能问题”的触角。 大多数性能问题来自于执行太多I / O或过多锁定或其中一个错误,并且这些都不适用于此问题。

我的一般方法是:

  1. 使用T []表示“静态”或“快照”式信息。用于调用.Add()无论如何都没有意义的东西,并且你不需要额外的方法List&lt; T&gt;给你。
  2. 接受IEnumerable&lt; T&gt;如果你真的不在乎你给的是什么而且不需要恒定的时间.Length / .Count。
  3. 仅返回IEnumerable&lt; T&gt;当你对输入进行简单的操作IEnumerable&lt; T&gt;或者当你特别想利用yield语法来懒散地工作时。
  4. 在所有其他情况下,请使用List&lt; T&gt;。它太灵活了。
  5. #4的推论:不要害怕ToList()。 ToList()是你的朋友。它强制IEnumerable&lt; T&gt;然后进行评估(当你堆叠几个where子句时很有用)。不要坚持下去,但是一旦你建立了完整的where子句,你就可以随意调用它,然后再对它进行预测(或者类似)。

    当然,这只是一个粗略的指导方针。请尝试在相同的代码库中遵循相同的模式 - 跳转的代码样式使维护编码人员更难以进入您的思维框架。