何时使用IList和何时使用List


179

我知道IList是接口,而List是具体类型,但是我仍然不知道何时使用每个接口。我现在正在做的是,如果我不需要使用接口的Sort或FindAll方法。我对吗?有没有更好的方法来决定何时使用接口或具体类型?


1
如果有人仍然想知道,我在这里找到最佳答案:stackoverflow.com/questions/400135/listt-or-ilistt
Crismogram

Answers:


174

我遵循两个规则:

  • 接受将起作用的最基本的类型
  • 返回用户需要的最丰富的类型

因此,在编写要使用集合的函数或方法时,请不要将其写入列表,而要写入IList <T>,ICollection <T>或IEnumerable <T>。即使System.Object也可以是T,所以通用接口即使对于异构列表也仍然可以使用。如果您决定在以后使用堆栈或其他数据结构,这样做将为您省去麻烦。如果在函数中需要做的只是遍历它,那么IEnumerable <T>实际上就是您所需要的。

另一方面,当从函数中返回对象时,您希望为用户提供尽可能丰富的操作集,而不必进行操作。因此,在这种情况下,如果内部是List <T>,则将副本作为List <T>返回。


43
您不应以不同的方式对待输入/输出类型。输入和输出类型应二者是最基本类型(优选接口),将支持的客户端的需求。封装依赖于尽可能少地告知客户端类的实现。如果返回具体的列表,则不能在不强制所有客户端重新编译/更新的情况下将其更改为其他更好的类型。

11
我不同意这两个规则...在这种情况下,我将使用大多数原始类型和特殊类型返回IList(更好的IEnumarable),并且应该在函数内部使用List。然后,当您需要“添加”或“排序”时,如果需要更多,则使用“集合”,然后使用“列表”。因此,我的硬性规则是:始终从IENumarable开始,如果您需要更多,则扩展...
ethem

2
为了方便起见,“两个规则”有一个名称:稳健性原则(又称Postel定律)
easoncxz 2015年

无论是关于返回最基本类型还是最丰富类型的辩论,无论是在辩论的哪一边,都需要考虑的一点是,当返回非常简化的接口时,使用代码有时(尽管不总是)使用if...else带有is关键字的链来进行计算。为其提供更丰富的类型,并最终强制转换为它并使用它。因此,不一定要通过使用基本界面来隐藏任何东西,而不仅仅是掩盖它。但是,使其变得更困难也可能使使用代码的作者对他们的使用方式进行三思。
Panzercrisis

6
我非常反对第二点,特别是在服务/ api边界的情况下。返回可修改的集合会给人一种印象,即这些集合是“实时”的,并且调用类似的方法,Add()并且Remove()可能会产生超出该集合的影响。返回只读接口,例如,IEnumerable通常是数据检索方法的方法。您的消费者可以根据需要将其投影到更丰富的类型中。
STW

56

FxCop检查的Microsoft准则不建议在公共API中使用List <T>-最好使用IList <T>。

顺便说一句,我现在几乎总是将一维数组声明为IList <T>,这意味着我可以一致地使用IList <T> .Count属性而不是Array.Length。例如:

public interface IMyApi
{
    IList<int> GetReadOnlyValues();
}

public class MyApiImplementation : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        List<int> myList = new List<int>();
        ... populate list
        return myList.AsReadOnly();
    }
}
public class MyMockApiImplementationForUnitTests : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        IList<int> testValues = new int[] { 1, 2, 3 };
        return testValues;
    }
}

3
我最喜欢这个说明/示例!
JonH

28

人们似乎总是忽略一个重要的事情:

您可以将纯数组传递给接受IList<T>参数的对象,然后可以调用IList.Add()并收到运行时异常:

Unhandled Exception: System.NotSupportedException: Collection was of a fixed size.

例如,考虑以下代码:

private void test(IList<int> list)
{
    list.Add(1);
}

如果按如下所示进行调用,则将获得运行时异常:

int[] array = new int[0];
test(array);

发生这种情况是因为将纯数组与 IList<T>违反了Liskov替换原理。

因此,如果您要致电,则IList<T>.Add()可能需要考虑使用List<T>而不是IList<T>


对于每个接口来说都是如此。如果您想坚持自己的观点,那么您可能会争辩说根本不要使用任何接口,因为可能会抛出该接口的某些实现。如果,在另一方面,考虑OP给出的建议更喜欢List<T>IList<T>,你也应该知道为什么原因,IList<T>值得推荐。(例如,blogs.msdn.microsoft.com / kcwalina / 2005/09/26 /…
Micha Wiedenmann

3
@MichaWiedenmann我的回答是针对您的通话时间IList<T>.Add()。我并不是说您不应该使用IList<T>-我只是指出可能的陷阱。(如果可以的话,我倾向于使用IEnumerable<T>IReadOnlyList<T>IReadOnlyCollection<T>优先选择IList<T>。)
马修·沃森

24

我会同意Lee的建议,但不要返回。

如果指定方法以返回接口,则意味着以后可以随意更改确切的实现,而无需使用任何消耗方法。我以为我永远都不需要从List <T>进行更改,而是不得不稍后进行更改以使用自定义列表库来提供它提供的额外功能。因为我只返回了IList <T>,所以使用该库的所有人员都无需更改其代码。

当然,这仅适用于外部可见的方法(即公共方法)。我甚至在内部代码中都亲自使用接口,但是由于您可以自行更改所有代码(如果您进行重大更改),因此这并不是绝对必要的。


21

IEnumerable
您应尝试使用适合您目的的最不特定的类型。
IEnumerable没有比的具体IList
你用IEnumerable,当你通过集合中的项目要循环。

IList
IList工具IEnumerable
你应该用IList当您需要按索引访问集合,添加和删除元素等时, ...

列出
List工具IList


3
出色,清晰的答案,我将其标记为有用。但是,我要补充的是,对于大多数开发人员而言,大多数时候,程序大小和性能上的细微差别都不值得担心:如果有疑问,只需使用List。
Graham Laight 2014年

9

始终最好使用最低的基本类型。这使您的界面的实现者或您的方法的使用者有机会在幕后使用他们喜欢的任何东西。

对于集合,您应该尽可能使用IEnumerable。这提供了最大的灵活性,但并不总是适用。


1
始终最好接受最低的基本类型。返回是一个不同的故事。选择可能有用的选项。所以您认为您的客户可能想要使用索引访问?禁止他们ToList()-ing返回的IEnumerable<T>已是列表的返回值,IList<T>而是返回一个。现在,客户可以毫不费力地受益于您所提供的服务。
蒂莫

5

如果您使用的是单个方法(有时甚至是单个类或程序集),并且外面没有人看您在做什么,请使用List的完整性。但是,如果要与外部代码进行交互(例如,从方法返回列表),则只想声明接口,而不必将自己绑定到特定的实现,尤其是如果您无法控制谁针对您的编译进行控制时之后的代码。如果您从一种具体的类型开始,并且决定更改为另一种,即使它使用相同的接口,除非您从一种接口或抽象基类型开始,否则您将破坏其他人的代码。


4

我认为这类事情没有严格的规定,但我通常会遵循使用最轻巧的方法的指导原则,直到绝对必要为止。

例如,假设您有一个Person班级和一个Group班级。一个Group实例有很多人,因此这里的List才有意义。当我在其中声明列表对象时,Group我将使用IList<Person>并将其实例化为List

public class Group {
  private IList<Person> people;

  public Group() {
    this.people = new List<Person>();
  }
}

而且,即使您甚至不需要任何东西,IList也可以随时使用IEnumerable。对于现代的编译器和处理器,我认为速度之间并没有真正的区别,所以这更多地是样式问题。


3
为什么不将其仅作为列表?我仍然不明白为什么您会从IList中获得奖金,然后在构造函数中将其收入List <>
chobo2 2011年

我同意,如果您显式创建List <T>对象,那么您会失去接口的优势吗?
The_Butcher

4

通常,最好使用最通用的可用类型,在这种情况下,最好使用IList甚至更好的IEnumerable接口,以便以后可以方便地切换实现。

但是,在.NET 2.0中,有一个烦人的事情-IList没有Sort()方法。您可以改用提供的适配器:

ArrayList.Adapter(list).Sort()

2

仅在需要时才应使用该接口,例如,如果列表被强制转换为List以外的IList实现。例如,当您使用NHibernate时,这是正确的,它在检索数据时将ILists转换为NHibernate bag对象。

如果List是您将用于特定集合的唯一实现,请随时将其声明为具体的List实现。


1

在通常遇到的情况下,我很少直接使用IList。

通常我只是将其用作方法的参数

void ProcessArrayData(IList almostAnyTypeOfArray)
{
    // Do some stuff with the IList array
}

这将使我可以对.NET框架中的几乎所有数组进行通用处理,除非它使用IEnumerable而不是IList,但有时会发生这种情况。

实际上,这取决于您需要的功能。我建议在大多数情况下使用List类。IList最适合需要创建自定义数组的情况,该数组可能具有一些非常具体的规则,希望将这些规则封装在集合中,这样您就不必重复自己了,但仍然希望.NET将其识别为列表。


1

AList对象使您可以创建列表,对其进行添加,删除,更新,对其进行索引等。只要您需要通用列表(在其中指定对象类型)就可以使用List。

另一方面,IList是一个接口。基本上,如果要创建自己的List类型,请说一个名为BookList的列表类,然后可以使用Interface为新类提供基本的方法和结构。IList适用于想要创建自己的实现List的特殊子类的情况。

另一个区别是:IList是一个接口,无法实例化。List是一个类,可以实例化。它的意思是:

IList<string> MyList = new IList<string>();

List<string> MyList = new List<string>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.