Collection <T>与List <T>在界面上应使用什么?


162

代码如下所示:

namespace Test
{
    public interface IMyClass
    {
        List<IMyClass> GetList();
    }

    public class MyClass : IMyClass
    {
        public List<IMyClass> GetList()
        {
            return new List<IMyClass>();
        }
    }
}

当我运行代码分析时,我得到以下建议。

警告3 CA1002:Microsoft.Design:将“ IMyClass.GetList()”中的“列表”更改为使用Collection,ReadOnlyCollection或KeyedCollection

我应该如何解决这个问题,这是什么好的做法?

Answers:


234

要回答为什么不这样的问题的“为什么”部分List<T>,原因是面向未来和API简单。

面向未来

List<T>不能通过子类化而易于扩展;它旨在快速实现内部实现。你会发现它的方法不是虚拟的,因此不能被覆盖,并且没有钩到其Add/ Insert/ Remove操作。

这意味着,如果您将来需要更改集合的行为(例如,拒绝人们尝试添加的空对象,或者在这种情况发生时执行其他工作,例如更新类状态),则需要更改类型在集合中,您可以返回一个可以子类化的类,这将是一个重大的接口更改(当然,更改诸如不允许null之类的语义也可能是接口更改,而诸如更新内部类状态之类的则不是)。

因此,通过返回,要么可以很容易的子类,如类Collection<T>或接口,例如IList<T>ICollection<T>或者IEnumerable<T>你可以改变你的内部实现是一个不同的集合类型,以满足您的需求,而不破坏消费者的代码,因为它仍然可以为返回他们期望的类型。

API简单性

List<T>包含许多有用的操作,例如BinarySearchSort等等。但是,如果您要公开的是一个集合,则很可能您控制列表的语义,而不是使用者的语义。因此,尽管您的班级内部可能需要这些操作,但班级的使用者不太可能希望(甚至应该)调用它们。

这样,通过提供一个更简单的集合类或接口,可以减少API用户看到的成员数量,并使他们更易于使用。


1
这种反应是死的。可以在这里找到关于该主题的另一本好书:blogs.msdn.com/fxcop/archive/2006/04/27/…–
senfo

7
我看到了您的第一点,但是我不知道我是否同意您的API简单性部分。
鲍里斯·卡伦斯

stackoverflow.com/a/398988/2632991这也是一篇不错的文章,关于“收藏夹”和“列表”之间的区别是什么。
El Mac

2
blogs.msdn.com/fxcop/archive/2006/04/27/…- >已死。我们是否有更新的信息?
马查多


50

我个人将声明它返回一个接口而不是一个具体的集合。如果您确实想要列表访问,请使用IList<T>。否则,请考虑ICollection<T>IEnumerable<T>


1
IList会扩展ICollection接口吗?
2008年

8
@Jon:我知道这很古老,但是您能在blogs.msdn.com/b/kcwalina/archive/2005/09/26/474010.aspx上评论Krzysztof所说的内容吗?特别是他的评论,We recommend using Collection<T>, ReadOnlyCollection<T>, or KeyedCollection<TKey,TItem> for outputs and properties and interfaces IEnumerable<T>, ICollection<T>, IList<T> for inputs. CA1002似乎与Krzysztof的评论一致。我无法想象为什么会建议使用具体的集合而不是接口,以及为什么要区分输入/输出。
尼尔森·罗瑟梅尔

3
@Nelson:很少有人要求呼叫者传递不可变列表,但是返回一个列表是合理的,这样他们就知道它绝对是不可变的。虽然不确定其他集合。希望有更多详细信息。
乔恩·斯基特

2
这不是针对特定情况。显然,一般而言ReadOnlyCollection<T>,输入没有任何意义。类似地,IList<T>正如输入所说,“我需要Sort()或IList具有的其他成员”,这对输出没有意义。但是我的意思是,为什么ICollection<T>建议将其作为输入和Collection<T>输出。为什么ICollection<T>按照您的建议将其用作输出?
纳尔逊·罗瑟梅尔

9
我认为这与模棱两可有关。 Collection<T>ReadOnlyCollection<T>从两个派生ICollection<T>(即不存在IReadOnlyCollection<T>)。如果返回接口,则不清楚是哪个接口以及是否可以修改它。无论如何,谢谢您的投入。这对我来说是一个很好的检查。
纳尔逊·罗瑟梅尔

3

我认为还没有人回答“为什么”部分...所以去了。之所以“为什么”使用“ Collection<T>而不是” List<T>是因为,如果公开一个List<T>,那么任何可以访问您对象的人都可以修改列表中的项目。而Collection<T>应该表明您正在制作自己的“添加”,“删除”等方法。

您可能不必担心它,因为您可能只为自己(或者可能是一些同事)编写接口。这是另一个可能有意义的示例。

如果您有公共阵列,请执行以下操作:

public int[] MyIntegers { get; }

您可能会认为,因为只有一个“获取”访问器,没有人可以弄乱这些值,但这不是事实。任何人都可以在其中更改值,如下所示:

someObject.MyIngegers[3] = 12345;

就个人而言,我只会List<T>在大多数情况下使用。但是,如果您要设计一个要提供给随机开发人员的类库,并且需要依赖于对象的状态……那么您将需要创建自己的Collection并将其从此处锁定: )


“如果将List <T>返回给客户端代码,则客户端代码修改集合时将永远不会收到通知。” -FX COP ...另请参阅“ msdn.microsoft.com/en-us/library/0fss9skc.aspx ” ...哇,看来我毕竟还算不错:)
Timothy Khouri

哇-几年后,我看到我在上述注释中粘贴的链接在其末尾加了引号,因此它不起作用... msdn.microsoft.com/en-us/library/0fss9skc.aspx
Timothy Khouri

2

这主要是关于抽象化自己的实现,而不是公开List对象以直接对其进行操作。

让其他对象(或人)直接修改对象的状态不是一个好习惯。考虑财产获取者/设定者。

收藏->对于普通收藏
ReadOnlyCollection->对于不应该修改的收藏
KeyedCollection->当您想要字典时。

如何解决它取决于您希望您的类做什么以及GetList()方法的目的。你能详细说明吗?


1
但是Collection <T>和KeyedCollection <,>也不是只读的。
nawfal 2014年

@nawfal我在哪里说呢?
chakrit 2014年

1
您声明不要让其他对象(或人)直接修改对象的状态并列出3种收集类型。禁止ReadOnlyCollection其他两个不遵守。
nawfal 2014年

这是指“良好做法”。请提供适当的上下文。由于OP希望理解警告,因此下面的列表仅陈述了此类类型的基本要求。然后,我继续向他询问有关的目的,GetList()以便能够更正确地帮助他。
chakrit 2014年

1
好的,我了解这一部分。但是根据我的说法,推理部分仍然不健全。您说的是Collection<T>有助于抽象化内部实现,并防止直接操纵内部列表。怎么样?Collection<T>只是一个包装器,对传递的同一实例进行操作。这是一个用于继承的动态集合,仅此而已(Greg的回答在此更相关)。
nawfal 2014年

1

在这种情况下,我通常尝试公开最少的实现。如果消费者不需要知道您实际上正在使用列表,则无需返回列表。通过按照Microsoft的建议返回一个Collection,可以隐藏您正在使用类别消费者的列表的事实,并将其与内部更改隔离开来。


1

自问到已经很久了,还有一些补充。

当列表类型从而List<T>不是从派生时Collection<T>,您将无法实现实现的受保护虚拟方法Collection<T>。这意味着如果对列表进行了任何修改,则派生类型将无法响应。这是因为List<T>假定您知道添加或删除项目的时间。能够响应通知是一项开销,因此List<T>没有提供。

如果外部代码可以访问您的收藏集,则您可能无法控制何时添加或删除项目。因此,Collection<T>提供了一种知道何时修改列表的方法。


0

我返回任何类似的东西都没有问题

this.InternalData.Filter(crteria).ToList();

如果我返回了内部数据的断开连接 副本或数据查询的分离结果-我可以安全地返回List<TItem>而不暴露任何实现细节,并允许以方便的方式使用返回的数据。

但这取决于我期望的消费者类型-如果这是我更喜欢返回的数据网格之类的东西IEnumerable<TItem> ,那么无论如何在大多数情况下都是复制的项目列表:)


-1

好吧,Collection类实际上只是其他集合的包装器类,以隐藏其实现细节和其他功能。我认为这与面向对象语言中的属性隐藏编码模式有关。

我认为您不必为此担心,但是,如果您真的想要取悦代码分析工具,请执行以下操作:

//using System.Collections.ObjectModel;

Collection<MyClass> myCollection = new Collection<MyClass>(myList);

1
对不起,那是一个错字。我提到Collection <MyClass>。我真的很期待获得C#4和通用协方差!
Tamas Czinege,

Collection<T>据我所知,包装保护本身或底层集合都不会受到保护。
nawfal 2014年

那段代码是“请使用代码分析工具”。我不认为@TamasCzinege所说的任何使用Collection<T>都会立即保护您的基础集合的地方。
chakrit 2014年
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.