C# 何时使用每个T[],列出<;T>;,IEnumerable<;T>;?

C# 何时使用每个T[],列出<;T>;,IEnumerable<;T>;?,c#,ienumerable,idioms,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> string[]things=

我通常会发现自己在做这样的事情:

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>
string[]things=arrayReturningMethod();
int index=things.ToList.FindIndex((s)=>s.Equals(“FOO”);
//用索引做些什么
返回东西。不同的()//返回一个IEnumerable
我发现所有这些类型/接口的混合有点让人困惑,这让我的潜在性能问题触角发痒(当然,在证明正确之前,我会忽略这一点)

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

编辑: 问题实际上有两个方面:

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

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


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


在这种情况下,与你描述的情况相似的每一种情况都是完全不同的,以防止关于转换你的类型的教条规则;但是,选择一个尽可能提供所需接口的数据结构,而不必将不必要的元素复制到新的数据结构,这通常是一个好的做法。

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

好吧,你有两个苹果和一个桔子在比较

这两个苹果是数组和列表

  • C#中的数组是一个内置垃圾收集的C样式数组。使用它们的好处是它们的开销很小,假设你不需要移动东西。糟糕的是,当您添加、删除内容或以其他方式更改数组时,它们的效率会降低,因为内存会被洗牌

  • 列表是C#风格的动态数组(类似于C++中的向量类)。这会带来更多的开销,但是当您需要大量移动东西时,它们会更有效,因为它们不会试图保持内存使用的连续性

我能给出的最好的比较是,数组与列表的关系就像字符串与StringBuilder的关系一样

橘子是“可数的”。这不是一个数据类型,而是一个接口。当类实现IEnumerable接口时,它允许在foreach()循环中使用该对象

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

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

这取决于应用程序。对于列表无法完成的数组,可以完成的工作非常少,因此我通常推荐使用列表。也许最好的办法是做出一个设计决定,你将使用其中一个,这样你就不必在两者之间切换。如果您依赖外部库,请将其抽象出来以保持一致的使用


希望这能消除一点迷雾。

在我看来,问题在于您没有费心学习如何搜索数组。提示:或取决于数组是否已排序


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

在实际出现性能问题之前,忽略“性能问题”天线是正确的。大多数性能问题都是由于执行了太多的I/O或太多的锁定或其中一个操作出错造成的,这些都不适用于此问题

我的一般做法是:

  • 使用T[]表示“静态”或“快照”样式的信息。用于调用.Add()无论如何都没有意义,并且不需要列表提供的额外方法的情况
  • 如果你真的不在乎别人给你什么,也不需要一个固定的时间,就接受IEnumerable。Length/.Count
  • 仅当您对输入IEnumerable进行简单操作或特别希望使用yield语法惰性地完成工作时,才返回IEnumerable
  • 在所有其他情况下,请使用列表。它太灵活了
  • #4的推论:不要害怕托利斯()。托利斯是你的朋友。它强制IEnumerable立即求值(在堆叠多个where子句时非常有用)。不要对它发疯,但是在你对它进行foreach(或类似)之前,一旦你建立了完整的where子句,就可以随意调用它


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

    关于性能

    • 从List转换到T[]需要将原始列表中的所有数据复制到新分配的数组中
    • 从T[]到List的转换还包括将原始列表中的所有数据复制到新分配的列表中
    • 从List或T[]转换为IEnumerable需要转换,这需要几个CPU周期
    • 从IEnumerable到List的转换需要向上转换,这也需要几个CPU周期
    • 从IEnumerable到T[]的转换也需要向上转换
    • 您不能将IEnumerable强制转换为t[]或List,除非
      public int[] DoSomething(IEnumerable<int> inputs)
      {
          //...
      }
      
      public List<int> DoSomethingElse(IList<int> inputs)
      {
          //...
      }