关于c#:何时使用IList以及何时使用List

When to use IList and when to use List

我知道i list是接口,list是具体的类型,但我仍然不知道何时使用每种类型。我现在要做的是,如果我不需要使用接口的sort或findall方法。我说的对吗?是否有更好的方法来决定何时使用接口或具体类型?


我遵循两条规则:

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

因此,在编写接受集合的函数或方法时,编写它不是为了获取列表,而是为了获取IList、ICollection或IEnumerable。即使对于异类列表,通用接口仍然可以工作,因为System.Object也可以是T。如果您决定使用堆栈或其他数据结构,那么这样做可以避免您的头痛。如果您在函数中需要做的只是通过它来实现foreach,那么IEnumerable实际上是您应该要求的全部内容。

另一方面,当从函数中返回一个对象时,您希望为用户提供尽可能丰富的操作集,而不必进行强制转换。因此,在这种情况下,如果它是一个内部列表,返回一个作为列表的副本。


由fxcop检查的Microsoft指南不鼓励在公共API中使用list-首选ilist

顺便说一下,我现在几乎总是将一维数组声明为ilist,这意味着我可以一致地使用ilist.count属性而不是array.length。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;
    }
}


人们总是忽略一件重要的事情:

可以将一个普通数组传递给接受IList参数的对象,然后可以调用IList.Add()并接收运行时异常:

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

例如,考虑以下代码:

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

如果按如下方式调用,则会得到运行时异常:

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

这是因为使用带有IList的平面阵列违反了liskov替换原则。

因此,如果您打电话给IList.Add(),您可能会考虑使用List,而不是IList


我同意李的建议,接受参数,但不返回。

如果您指定方法来返回一个接口,这意味着您可以在以后自由地更改确切的实现,而不必知道消耗方法。我认为我不需要从列表中进行更改,但后来必须进行更改才能使用自定义列表库来提供额外的功能。因为我只返回了一个ilist,所以使用库的人都不需要更改代码。

当然,这只需要应用于外部可见的方法(即公共方法)。我个人甚至在内部代码中也会使用接口,但由于您能够自己更改所有代码,所以如果您进行破坏性的更改,这并不是严格必要的。


可枚举的您应该尝试使用最不具体的类型来满足您的目的。IEnumerable不如IList具体当要循环访问集合中的项时,可以使用IEnumerable。

伊利斯特IList实现IEnumerable当需要按索引访问集合、添加和删除元素等时,应使用ilist。

表列表实现IList


最好使用尽可能低的基类型。这就给了界面的实现者,或者方法的使用者,在幕后使用他们喜欢的任何东西的机会。

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


如果您在一个方法内工作(或者在某些情况下甚至在一个类或程序集内工作),而外部没有人会看到您在做什么,那么使用列表的完整性。但是,如果您正在与外部代码交互,比如当您从方法返回列表时,那么您只希望声明接口,而不必将自己绑定到特定的实现,特别是如果您无法控制之后谁编译代码。如果您从一个具体的类型开始,并决定改为另一个类型,即使它使用相同的接口,除非您从一个接口或抽象基类型开始,否则您将破坏其他人的代码。


使用最通用的可用类型(在本例中是IList,甚至更好的是IEnumerable接口)通常会更好,以便以后方便地切换实现。

然而,在.NET 2.0中,有一件烦人的事情-ilist没有sort()方法。您可以使用提供的适配器:

1
ArrayList.Adapter(list).Sort()

我不认为这类事情有硬性和快速性的规则,但我通常以尽可能轻的方式进行指导,直到绝对必要为止。

例如,假设您有一个Person类和一个Group类。一个Group实例有很多人,所以这里的列表是有意义的。当我在Group中声明list对象时,我将使用IList并将其实例化为List

1
2
3
4
5
6
7
public class Group {
  private IList<Person> people;

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

而且,如果您甚至不需要IList中的所有内容,您也可以使用IEnumerable。对于现代的编译器和处理器,我不认为有什么真正的速度差异,所以这只是风格问题。


a list对象允许您创建一个列表,向其添加内容,删除它,更新它,索引到它等等。只要您想要一个通用列表,在其中指定对象类型,就可以使用列表。

另一方面,IList是一个接口。基本上,如果您想创建自己的列表类型,比如一个名为booklist的列表类,那么您可以使用该接口为新类提供基本的方法和结构。当您想创建自己的实现列表的特殊子类时,可以使用IList。

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

1
2
3
IList<string> MyList = new IList<string>();

List<string> MyList = new List<string>

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

如果列表是您将用于某个集合的唯一实现,那么可以将其声明为具体的列表实现。


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

通常我只是用它作为方法的参数

1
2
3
4
void ProcessArrayData(IList almostAnyTypeOfArray)
{
    // Do some stuff with the IList array
}

这将允许我对.NET框架中的几乎任何数组执行一般处理,除非它使用IEnumerable而不是IList,这有时会发生。

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