c# - 对返回类型使用 ReadOnlyCollection<T> 与 List<T> 与数组

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

我发布了一个similar question刚才关于参数,提到了an article与提倡使用 IReadOnlyCollection<T>这个问题更相关在 IEnumerable<T> .

Lately, I’ve been considering the merits and demerits of returning IEnumerable<T>.

On the plus side, it is about as minimal as an interface gets, so it leaves you as method author more flexibility than committing to a heavier alternative like IList<T> or (heaven forbid) an array.

However, as I outlined in the last post, an IEnumerable<T> return entices callers to violate the Liskov Substitution Principle. It’s too easy for them to use LINQ extension methods like Last() and Count(), whose semantics IEnumerable<T> does not promise.

What’s needed is a better way to lock down a returned collection without making such temptations so prominent. (I am reminded of Barney Fife learning this lesson the hard way.)

Enter IReadOnlyCollection<T>, new in .NET 4.5. It adds just one property to IEnumerable<T>: the Count property. By promising a count, you assure your callers that your IEnumerable<T> really does have a terminus. They can then use LINQ extension methods like Last() with a clear conscience.

然而,this article提倡使用ReadOnlyCollection<T> (和其他替代方案)用于您想要保护成员的情况。

所以,我和一个同事讨论过这个问题,我们意见不一。

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

我的观点是ReadOnlyCollection<T>向调用者指示集合通常应保持原样。如果他们想更改它,那么他们就有责任将其显式转换为 IList<T>。或调用ToList在上面。

Microsoft guidelines具体状态:

In general, prefer 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.
    }
}

所以用一个例子来说明这个困境,如果我们取 GetMemberArrayAsIs ,ReSharper 告诉我将返回类型更改为 IEnumerable<T>如果调用代码仅枚举返回值一次。如果我多次枚举返回值,那么我必须保持原样,以避免可能出现的多次枚举警告。然而,这就是我的问题所在。我应该返回 ReadOnlyCollection<T> 而不是返回数组吗?遵守我对 Microsoft 准则的理解?这需要我实例化一个 ReadOnlyCollection<T>如图GetMemberArrayAsReadOnlyCollection .或者,我们是否应该像我的同事认为的那样返回数组 (GetMemberArrayAsIs)?

或者,我可以坚持返回 IEnumerable<T>并让调用者有责任在多次使用集合之前进行枚举,但我认为微软更喜欢使用 ReadOnlyCollection<T>就是为了避免这种情况。

如果是 List<T> , 困境稍微复杂一些,因为它有可能被改变,如果它是成员,这是一个问题。在这种情况下,我当然应该返回 ReadOnlyCollection<T> (GetMemberListAsReadOnlyCollection)。但是对于本地列表,我应该返回 ReadOnlyCollection<T> 吗?遵守我对 Microsoft 准则的理解?这需要我实例化一个 ReadOnlyCollection<T>如图GetLocalListAsReadOnlyCollection .或者,我们是否应该像我的同事认为的那样返回列表 (GetMemberListAsIs)?

我希望这能为这个问题增加更多的背景,并且在某些方面它是相当哲学的。但其中很多是关于应如何解释 Microsoft 指南,以及是否应由提供商承担转换为 ReadOnlyCollection<T> 的责任。 ,或者让消费者在多次使用之前枚举返回的值(使用 ToArray ),或者实际上两者都没有并按原样返回。

最佳答案

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

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

  • 调用 ToList不需要对同一输出进行多次枚举。
  • 界限明确
  • 使用IReadOnlyList<T> ,您可以使用 for 进行枚举循环而不是 foreach ,这在某些极端情况下可能是有利的。

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

  • 您的 API 将无法再返回 IEnumerable<T>以流式传输方式(例如从 StreamIDbDataReader 等读取)。这意味着在您的代码中引入重大更改。

关于使用IList<T>对比IReadOnlyList<T>IReadOnlyCollection<T> ,只要您总是返回列表的副本就可以了。如果您有一个保存在内存中的列表,您不希望调用者修改它。

关于c# - 对返回类型使用 ReadOnlyCollection<T> 与 List<T> 与数组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49281644/

相关文章:

c# - ASP.NET 中 C# 的人脸检测

c# - 一种转换为对象实际类型的干净方法

c# - 使用 HttpClient 将文件上传到 ASP.NET WebApi 2

c++ - 在 C++ 中创建动态对象的动态数组

arrays - Haskell - 重现 numpy 的 reshape

c# - 更改 DataGridTemplateColumn 内容样式

c - 无维度的全局整数数组

list - 交替应用输入一元函数的 Haskell 函数

python - 在 Python 中将计数器/索引添加到列表列表中

perl - 如何按元素对两个列表求和