使用ReadOnlyCollection <t> vs List <t> vs数组返回类型

时间:2018-03-14 15:25:41

标签: c# arrays list return-type readonly-collection

前一段时间我发布了一个关于参数的similar question,它提到an article问题更相关,该问题提倡使用IReadOnlyCollection<T>而不是{{} 1}}。

  

最近,我一直在考虑回归的优点和缺点   IEnumerable<T>

     

从好的方面来说,它与接口一样小,所以它   让你作为方法作者比提交更灵活   较重的替代方案,如IEnumerable<T>或(heaven forbid)数组。

     

但是,正如我在the last post中所述,IList<T>返回   诱使来电者违反Liskov Substitution Principle。它太   他们很容易使用IEnumerable<T>Last()等LINQ扩展方法,   其语义Count()不承诺。

     

需要更好的方法来锁定返回的集合   没有让这种诱惑如此突出。 (我想起了巴尼   Fife努力学习这一课。)

     

在.NET 4.5中输入新的IReadOnlyCollection<T>。它只增加了一个   属性IEnumerable<T>IEnumerable<T>属性。通过承诺计数,   你保证你的呼叫者你的Count确实有一个   总站。然后,他们可以使用像IEnumerable<T>这样的LINQ扩展方法   清醒的良心。

但是,this article提倡在您希望保护成员的情况下使用Last()(和其他替代方案)。

所以,我和一位同事讨论了这个问题,我们有不同的意见。

他建议一个方法应该返回它拥有的任何东西(例如ReadOnlyCollection<T>或数组),如果方法产生它并且之后更改它的调用者将对其他任何东西没有影响(即,它不是成员并且不属于生命周期大于方法调用的任何事物。)

我的观点是List<T>向调用者表明该集合通常应保持原样。如果他们想要更改它,那么他们有责任明确地将其转换为ReadOnlyCollection<T>或致电IList<T>

Microsoft guidelines处于特定状态:

  

一般情况下,请选择ToList

但除了声明使用ReadOnlyCollection<T>返回读/写集合之外,它似乎没有定义一般情况。但是,我的同事认为消费者可能想要添加到集合中,而我们不关心他们是否做了返回读/写集合的定义?

修改

在回答一些回复时,为了尝试在我的问题中添加更多上下文,我在下面放了一些代码,演示了与我的问题有关的评论的各种场景:

Collection<T>

所以使用一个例子来证明这个困境,如果我们采用class Foo { private readonly int[] array = { 1 }; private readonly List<int> list = new List<int>(new[] {1}); // Get member array. public int[] GetMemberArrayAsIs() => array; public IEnumerable<int> GetMemberArrayAsEnumerable() => array; public ReadOnlyCollection<int> GetMemberArrayAsReadOnlyCollection() => new ReadOnlyCollection<int>(array); // Get local array. public int[] GetLocalArrayAsIs() => new[] { 1 }; public IEnumerable<int> GetLocalArrayAsEnumerable() => new[] { 1 }; public ReadOnlyCollection<int> GetLocalArrayAsReadOnlyCollection() => new ReadOnlyCollection<int>(new[] { 1 }); // Get member list. public Collection<int> GetMemberListAsIs() => new Collection<int>(list); public IEnumerable<int> GetMemberListAsEnumerable() => array; public ReadOnlyCollection<int> GetMemberListAsReadOnlyCollection() => new ReadOnlyCollection<int>(array); // Get local list. public Collection<int> GetLocalListAsIs() => new Collection<int>(new[] { 1 }); public IEnumerable<int> GetLocalListAsEnumerable() => new List<int>(new[] { 1 }); public ReadOnlyCollection<int> GetLocalListAsReadOnlyCollection() => new List<int>(new[] { 1 }).AsReadOnly(); } class FooTest { void Test() { var foo = new Foo(); int count; // Get member array. var array1 = foo.GetMemberArrayAsIs(); // ReSharper encourages to make the return type IEnumerable<T>. count = array1.Length; // ...unless we do this. var enumerable1 = foo.GetMemberArrayAsEnumerable(); enumerable1.Concat(enumerable1); // Warning of possible multiple enumeration. var roc1 = foo.GetMemberArrayAsReadOnlyCollection(); // ReSharper encourages to make the return type IEnumerable<T>. count = roc1.Count; // ...unless we do this. // Get local array. var array2 = foo.GetLocalArrayAsIs(); // ReSharper encourages to make the return type IEnumerable<T>. count = array2.Length; // ...unless we do this. var enumerable2 = foo.GetLocalArrayAsEnumerable(); enumerable2.Concat(enumerable2); // Warning of possible multiple enumeration. var roc2 = foo.GetLocalArrayAsReadOnlyCollection(); // ReSharper encourages to make the return type IEnumerable<T>. count = roc2.Count; // ...unless we do this. // Get member list. var list1 = foo.GetMemberListAsIs(); // ReSharper encourages to make the return type IEnumerable<T>. count = list1.Count; // ...unless we do this. list1.Add(2); // This affects the Foo object as the collection is a member. DANGEROUS! var enumerable3 = foo.GetMemberListAsEnumerable(); enumerable3.Concat(enumerable3); // Warning of possible multiple enumeration. var roc3 = foo.GetMemberListAsReadOnlyCollection(); // ReSharper encourages to make the return type IEnumerable<T>. count = roc3.Count; // ...unless we do this. // Get local list. var list2 = foo.GetLocalListAsIs(); // ReSharper encourages to make the return type IEnumerable<T>. count = list2.Count; // ...unless we do this. list2.Add(2); // This doesn't affect the Foo object as the collection was produced by the method. var enumerable4 = foo.GetLocalListAsEnumerable(); enumerable4.Concat(enumerable4); // Warning of possible multiple enumeration. var roc4 = foo.GetLocalListAsReadOnlyCollection(); // ReSharper encourages to make the return type IEnumerable<T>. count = roc4.Count; // ...unless we do this. } } ,ReSharper告诉我如果调用代码只返回一次返回值,则将返回类型更改为GetMemberArrayAsIs。如果我多次枚举返回的值,那么我必须保持原样,以避免可能的多次枚举警告。但是,这就是我的问题所在。我应该返回IEnumerable<T>以符合我对Microsoft指南的理解,而不是返回数组吗?这需要我实例化ReadOnlyCollection<T>,如ReadOnlyCollection<T>所示。或者,我们应该像我的同事所认为的那样返回数组(GetMemberArrayAsReadOnlyCollection)吗?

或者,我可以坚持返回GetMemberArrayAsIs并将调用者的责任放在枚举之前多次使用该集合,但我认为Microsoft使用IEnumerable<T>的偏好是为了避免这一点。< / p>

ReadOnlyCollection<T>的情况下,这种困境稍微复杂一点,因为如果它是一个成员,它可能会被改变,这是一个问题。在这种情况下,我当然应该返回List<T>ReadOnlyCollection<T>)。但就本地列表而言,我应该返回GetMemberListAsReadOnlyCollection以符合我对Microsoft指南的理解吗?这需要我实例化ReadOnlyCollection<T>,如ReadOnlyCollection<T>所示。或者,我们应该像我的同事所认为的那样返回列表(GetLocalListAsReadOnlyCollection)吗?

我希望这会为这个问题增加更多的背景,而且在某些方面它具有相当的哲学性。但其中很多内容都是关于如何解释Microsoft指南,以及是否应该在提供程序上将onus转换为GetMemberListAsIs,或者使用者在使用多次之前枚举返回的值(使用{ {1}}),或者实际上都没有,并按原样返回。

1 个答案:

答案 0 :(得分:2)

这个问题可能会被关闭,因为它含糊不清并且没有单一的正确答案,但无论如何我都会尝试。

我想说,如果你返回一个List<T>T[]的对象,你可能希望将其公开为至少IReadOnlyCollection<T>或更好IReadOnlyList<T>索引器支持。这样做的好处是:

  • 不需要调用ToList来执行相同输出的多个枚举。
  • 界限明确定义
  • 使用IReadOnlyList<T>,您可以使用for循环进行枚举,而不是foreach,这在某些极端情况下可能会有所帮助。

我看到的主要缺点是,根据实施情况,这可能是一个很大的缺点:

  • 您的API将无法再以流式方式返回IEnumerable<T>(例如,从StreamIDbDataReader等处阅读)。这意味着要在代码中引入重大变化。

至于使用IList<T> vs IReadOnlyList<T>IReadOnlyCollection<T>,只要你总是可以返回列表的副本,这应该是完全正常的。如果你有一个你在内存中保留的列表,你不希望调用者修改它。