为什么IEnumerable <T> .ToList <T>()返回List <T>而不是IList <T>?

Wal*_*fee 67 c# linq ienumerable interface

扩展方法ToList()返回一个List<TSource>.遵循相同的模式,ToDictionary()返回一个Dictionary<TKey, TSource>.

我很好奇,为什么这些方法没有键入他们的返回值IList<TSource>IDictionary<TKey, TSource>分别.这似乎更奇怪,因为将ToLookup<TSource, TKey>其返回值作为接口而不是实际实现.

使用dotPeek或其他反编译器查看这些扩展方法的来源,我们看到以下实现(显示ToList()因为它更短):

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source) { 
   if (source == null) throw Error.ArgumentNull("source");
   return new List<TSource>(source); 
}
Run Code Online (Sandbox Code Playgroud)

那么为什么这个方法将其返回值键入为接口的特定实现而不是接口本身呢?唯一的变化是返回类型.

我很好奇,因为IEnumerable<>扩展在签名上非常一致,除了这两种情况.我一直认为这有点奇怪.

此外,为了使事情更加混乱,州的文件ToLookup():

根据指定的键选择器函数从IEnumerable创建Lookup.

但是返回类型是ILookup<TKey, TElement>.

Edulinq,Jon Skeet提到返回类型List<T>不是IList<T>,而是不进一步触及主题.
广泛的搜索没有得到答案,所以在这里我问你:

是否有任何设计决策背后没有键入返回值作为接口,还是只是偶然?

小智 49

返回List<T>具有的优点是List<T>,不IList<T>容易使用那些不是其一部分的方法.有很多的事情可以做一个List<T>,你不能用做IList<T>.

相比之下,Lookup<TKey, TElement>只有一个可用的方法ILookup<TKey, TElement>没有(ApplyResultSelector),你可能不会最终使用它.

  • 这可能就是答案,但是,如果没有设计过程中的干扰记录,我们怎能知道.(1) (7认同)
  • +1正确答案.但是,Microsoft应该*真正*采用特定于`List <T>`的那些方法并将它们更改为对`IList <T>`进行操作的扩展方法 - 这样,**所有**`IList <T>`实现可以受益于`Reverse()`,`Sort()`等,而不仅仅是`List <T>`!几年前我在MS Connect上建议了这个; 它得到了很多赞成,他们说这是一个好主意,但一直把它推到下一个版本.然后他们[删除了建议](https://connect.microsoft.com/VisualStudio/feedback/details/552410):( (4认同)
  • 同意,问题的开头是"为什么",而不是"为什么会这样".我相信没有人把我的答案误解为理由而不是理由.:) (2认同)

Mat*_*zer 12

这些决定可能会让人感到随意,但我想这会ToList()返回List<T>而不是接口,因为List<T>它们都是实现,IList<T>但它会添加其他不存在于常规IList<T>对象中的成员.

例如,AddRange().

看看IList<T>应该实现什么(http://msdn.microsoft.com/en-us/library/5y536ey6.aspx):

public interface IList<T> : ICollection<T>, 
    IEnumerable<T>, IEnumerable
Run Code Online (Sandbox Code Playgroud)

List<T>(http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx):

public class List<T> : IList<T>, ICollection<T>, 
    IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>, 
    IEnumerable
Run Code Online (Sandbox Code Playgroud)

也许您自己的代码不需要IReadOnlyList<T>,IReadOnlyCollection<T>或者ICollection,但.NET Framework和其他产品上的其他组件可能依赖于更专业的列表对象,这就是.NET开发团队决定不返回接口的原因.

不要觉得返回界面是最佳做法.如果您的代码或第三方代码需要这样的封装.


Ser*_*rvy 8

有刚有许多优点List超过一个IList.首先,List有方法IList没有.您还知道实现是什么允许您推断它的行为方式.你知道它可以有效地添加到最后,但不是开始,你知道它的索引器非常快,等等.

您无需担心您的结构被更改为LinkedList并破坏应用程序的性能.当涉及到这样的数据结构时,在很多情况下确实知道数据结构是如何实现的,而不仅仅是它所遵循的合同,这一点非常重要.它的行为不应该改变.

你也无法传递一个IList接受a的方法List,这是你看到的很多东西. ToList经常使用,因为这个人真的需要一个实例List,以匹配他们无法控制的签名,并且IList没有帮助.

然后我们问自己回归有什么好处IList.好吧,我们可能会返回列表的其他一些实现,但如前所述,这可能会产生非常有害的后果,几乎可以肯定会比使用任何其他类型更多.它可能会给你温暖的模糊使用一个接口而不是一个实现,但即使这是我认为在这种情况下不是一个良好的心态(一般或).通常,返回接口通常不优选返回具体实现."在你所接受的内容中保持自由,并在你提供的内容中具体说明." 在可能的情况下,方法的参数应该是定义您需要的最少功能的接口,您的调用者可以在任何实现您需要的实现中传递,并且您应该提供实现的具体内容,因为调用者是"允许"看到它们可以像对象那样对结果做同样多的事情.传递接口限制了该值,这只是偶尔您想要做的事情.

所以现在我们继续,"为什么回来ILookup而不是Lookup?" 好吧,首先Lookup不是公共课.有没有LookupSystem.Collections.*.该Lookup所通过LINQ暴露类公开不公开的构造.你不能够使用类,除非通过ToLookup.它还没有公开任何尚未透露的功能ILookup.在这种特殊情况下,他们专门围绕这个精确的方法(ToLookup)设计了接口,Lookup该类是专门为实现该接口而设计的类.因为所有这些,所讨论的几乎所有要点List都不适用于此.返回是否是一个问题Lookup,不,不是真的.在这种情况下,它无论如何都无关紧要.


Oli*_*bes 6

在我看来List<T>,通过方法名称说明返回a 是合理的ToList.否则它必须被命名ToIList.这种方法的目的是将非IEnumerable<T>特定类型转换为特定类型List<T>.

如果你有一个非特定名称的方法GetResults,那么返回类型就像IList<T>或者IEnumerable<T>对我来说似乎是合适的.


如果你看一下Lookup<TKey, TElement>带反射器的类的实现,你会看到很多internal成员,只有LINQ本身才能访问它们.没有公共构造函数,Lookup对象是不可变的.因此,Lookup直接暴露没有任何优势.

Lookup<TKey, TElement> class似乎是LINQ内部的一种,并不适合公共使用.

  • @Waldfee:可能是因为它返回的对象在逻辑上是不可变的,这意味着工厂可以根据它包含的内容自由地优化返回类型.例如,如果要返回的实例只有一个项目,那么为它构建一个哈希表是愚蠢的.让`ToLookup`返回一个接口使得它可以在这种情况下返回一个不同的具体类型; 即使目前的实施没有优化这种情况,未来的实施也可以这样做. (3认同)
  • 但是为什么`ToLookup()`的返回类型是接口呢? (2认同)