我知道IList是接口类型,而List是具体类型,但我仍然不知道何时应该使用它们中的哪一个。现在我做的是,如果我不需要Sort或FindAll方法,我就使用接口。我是对的吗?有没有更好的方法来决定何时使用接口或具体类型?
我遵循两个规则:
因此,在编写接受集合的函数或方法时,不要使用List,而是使用IList<T>、ICollection<T>或IEnumerable<T>。即使对于异构列表,通用接口仍将起作用,因为System.Object也可以是T。如果您决定在更深处使用Stack或其他数据结构,则这样做会使您省去麻烦。如果函数中需要做的只是循环遍历,则真正需要请求的只是IEnumerable<T>。
另一方面,当从函数中返回对象时,您希望给用户提供尽可能丰富的操作集,而不必进行转换。因此,在这种情况下,如果内部是List<T>,则将其作为List<T>的副本返回。
Add()
和 Remove()
这样的方法可能会产生超出集合之外的影响。对于数据检索方法,通常最好返回只读接口,例如 IEnumerable
。您的使用者可以按需将其投射到更丰富的类型中。 - STWIList<T>
是唯一允许快速索引访问 []
的集合接口。通常我的要求之一是返回这样一个索引集合。我的另一个要求是能够更改实际的集合(列表、数组等),而不需要 API 更改,这意味着返回类型应该是一个接口。这些要求带我们来到了 IList<T>
。不幸的是,这也授予了 Add()
,以及其他方法(甚至对于数组也会 抛出 异常!)。因此,集合接口并不是理想的选择。或者,通过调用 resultList.AsReadOnly()
返回一个 ReadOnlyCollection<T>
。 - Timo根据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;
}
}
IEnumerable
您应该尽可能使用最不具体的类型来满足您的需求。
IEnumerable
比IList
不太具体。
当您想遍历集合中的项目时,可以使用IEnumerable
。
IList
IList
实现了IEnumerable
。
当您需要通过索引访问集合、添加和删除元素等操作时,应使用IList
。
List
List
实现了IList
。
有一件重要的事情,人们似乎总是忽略:
你可以将普通数组传递给接受 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>
。
List<T>
而不是IList<T>
,你也应该了解为什么推荐使用IList<T>
。(例如https://blogs.msdn.microsoft.com/kcwalina/2005/09/26/why-we-dont-recommend-using-listt-in-public-apis/) - Micha WiedenmannIList<T>.Add()
方法的情况。我并不是说你不应该使用 IList<T>
- 我只是指出了可能会遇到的问题。如果可以的话,我通常更喜欢使用IEnumerable<T>
或IReadOnlyList<T>
或IReadOnlyCollection<T>
而不是IList<T>
。 - Matthew Watson尽可能使用最低基础类型,这样你的接口实现者或方法使用者就有机会在幕后使用他们喜欢的任何内容。
对于集合,应尽可能使用IEnumerable。这提供了最大的灵活性,但并不总是适用。
IEnumerable<T>
进行ToList()
操作,而是返回一个IList<T>
。现在,客户可以轻松地从您提供的内容中受益。 - Timo如果你只在单个方法内(甚至只在某些情况下的单个类或程序集内)工作,而且没有人外部会看到你在做什么,那么使用List的完整性。但是如果你要与外部代码交互,例如从方法返回列表时,那么你只需要声明接口而不必将自己绑定到特定实现,特别是如果你无法控制谁编译你的代码之后。如果你最初使用了具体类型,并决定更改为另一个类型,即使它使用相同的接口,你也会破坏其他人的代码,除非你最初使用了接口或抽象基础类型。
在大多数情况下,建议您使用最常用的通用类型,即IList或者更好的IEnumerable接口,以便稍后可以方便地切换实现方式。
然而,在.NET 2.0中,有一件令人烦恼的事情 - IList没有一个Sort()方法。您可以使用提供的适配器代替:
ArrayList.Adapter(list).Sort()
public class Group {
private IList<Person> people;
public Group() {
this.people = new List<Person>();
}
}
而且,如果您甚至不需要 IList
中的所有内容,您也可以始终使用 IEnumerable
。随着现代编译器和处理器的发展,我认为实际上没有速度差异,因此这更多只是一种风格问题。
只有在需要时,例如,如果您的列表被转换为除List以外的IList实现,才应使用接口。例如,当您使用NHibernate时,它会将ILists转换为NHibernate bag对象来检索数据。
如果List是您将永远使用的某个集合的唯一实现,请随意将其声明为具体的List实现。