何时使用T [],List <T>,IEnumerable <T>中的每一个?

Vin*_*vic 8 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>
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 什么时候直接使用IEnumerable接口或数组或列表(或任何其他IEnumerable实现类型)(接受参数时)?

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

Rei*_*aka 8

关于表现......

  • 从List转换为T []涉及将原始列表中的所有数据复制到新分配的数组.
  • 从T []转换为List还涉及将原始列表中的所有数据复制到新分配的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 <T>值),它只能处理List值,所以用户首先必须转换,如果他们使用的话在[].同样,如果函数是PrintValues(T []值).但如果它是PrintValues(IEnumerable <T>值),它将能够处理Lists,T [],堆栈,哈希表,字典,字符串,集合等 - 任何实现IEnumerable的集合,几乎每个采集.

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

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

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


Kir*_*oll 7

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