【问题标题】:IReadOnlyCollection vs ReadOnlyCollectionIReadOnlyCollection 与 ReadOnlyCollection
【发布时间】:2017-12-23 04:54:43
【问题描述】:

关于 SO 已经有几个类似的问题,但我发现没有一个问题真正涉及到这个特定主题,所以就在这里......

我的理解是,应该始终尝试通过具体类返回接口。不会深入探讨其背后的原因,关于这方面的内容已经很多了。

但是对于 IReadOnlyCollectionReadOnlyCollection 的情况,我不确定是否应该遵循该规则。

IReadOnlyCollection 可以轻松转换为 List,这...嗯...打破合同承诺的ReadOnly 方面。

ReadOnlyCollection 但是不能转换为List,但这意味着返回一个具体的类。

从长远来看,这真的重要吗?在我看来,在大多数情况下ReadOnly*/IReadOnly* 对象仅由方法或只读属性返回。

因此,即使用户决定将其转换为其他东西(在 IReadOnly* 对象的情况下)或使用 LINQ 从中创建某种集合(在 ReadOnly* 对象的情况下),暴露ReadOnly*/IReadOnly* 对象的类真的不可能接受它。

那么这里的建议是什么,返回一个IReadOnly* 接口还是一个具体的ReadOnly* 类实例?

【问题讨论】:

  • 当您承诺返回IReadOnlyCollection<T> 并返回List<T> 时,您害怕发生什么?
  • “演员”问题是不切实际的...例如 LINQ 返回大量 IEnumerable<T> 但每个人都调用 ToList 来获得列表表示。这是调用者的错,该方法只承诺IReadOnlyCollection<T>。顽皮的调用者甚至可以使用反射来破解 IReadOnlyCollection<T> 的内部内容,但这是他们的问题。

标签: c# .net linq


【解决方案1】:

微软的指导方针herestate:

✓ 务必使用ReadOnlyCollectionReadOnlyCollection<T> 的子类,或者在极少数情况下使用IEnumerable<T> 来表示表示只读集合的​​属性或返回值。

所以,基本上,你应该返回ReadOnlyCollection<T>。它在其他情况下指定接口IEnumerable,因此如果它打算使用接口IReadOnlyCollection<T>,它会这样说明。

【讨论】:

  • 引用的页面是 2008 年书籍的再版,但这些接口直到 2012 年才作为 .NET 4.5 的一部分存在。已创建一个问题以明确这一点:github.com/dotnet/docs/issues/26464
【解决方案2】:

您绝对应该尝试让您的公共方法返回接口。

如果您担心类的调用者会强制转换和修改您的内部结构,例如在本例中,不应从外部触及类的内部队列:

public class QueueThing
{
    private List<QueueItem> _cantTouchThis;

    public IReadOnlyCollection<QueueItem> GetQueue()
    {
        return _cantTouchThis;
    }
}

然后你可以使用AsReadOnly()返回一个新的ReadOnlyList&lt;T&gt;,来源于私有List&lt;T&gt;

public class QueueThing
{
    private List<QueueItem> _cantTouchThis;

    public IReadOnlyCollection<QueueItem> GetQueue()
    {
        return _cantTouchThis.AsReadOnly();
    }
}

现在调用者可以随心所欲地转换返回值,他们将无法修改_cantTouchThis 成员(当然,除非他们打算使用反射,但无论如何,所有赌注都已关闭)。

鉴于许多类型都可以实现一个接口,这种方法的用户绝对不应该假设将方法的返回值转换为任何具体类型是安全的。

【讨论】:

  • 对,但问题是返回IReadOnlyCollection&lt;T&gt;还是ReadOnlyCollection&lt;T&gt;。您正在返回 IReadOnlyCollection&lt;T&gt;
  • 您的问题是关于调用者转换返回值的问题。我问你为什么担心这个,鉴于你没有回应,我假设了一些事情。虽然这个答案可能不适用于您的情况,但它可能适用于其他有同样问题的人。
  • 你的假设是正确的,这个例子是完美的。阅读我添加到@Magnus 答案的评论,了解我的意思。
  • 那么,您是否阅读了 “然后您可以在我的回答中使用 AsReadOnly() 返回一个新的 ReadOnlyList 部分?如果是这样,我不明白你不明白什么。 :)
  • 来电者收到IReadOnlyCollection&lt;T&gt; 回复。由于List&lt;T&gt; 实现了IReadOnlyCollection&lt;T&gt;,我相信调用者可以假设他可以将IReadOnlyCollection&lt;T&gt; 转换为List&lt;T&gt;,在这种情况下,这将失败。但是返回一个具体的类型,即ReadOnlyCollection&lt;T&gt; 可以清楚地表明无法转换为List&lt;T&gt;
【解决方案3】:

IReadOnlyCollection&lt;T&gt; 只能转换为 List&lt;T&gt; 如果基础对象属于该类型。例如ReadOnlyCollection&lt;T&gt; 也实现了IReadOnlyCollection&lt;T&gt;

所以我的建议,返回IReadOnlyCollection&lt;T&gt;,如果你担心调用者会错误地将它转换为不应该的东西,请确保底层类型是ReadOnlyCollection&lt;T&gt;

public IReadOnlyCollection<User> GetUsers()
{
   return new ReadOnlyCollection<User>();
}


但是返回 IReadOnlyCollection&lt;T&gt; 应该足以让函数调用者理解它应该是只读的。
请注意,您永远无法使用ReadOnlyCollection&lt;T&gt; 完全保护您的代码,调用者仍然可以使用反射来访问内部列表并对其进行操作。
在这种情况下,唯一的选择是在列表中创建一个副本并将其返回。

【讨论】:

  • 但是调用者不会认为既然List&lt;T&gt; 实现了IReadOnlyCollection&lt;T&gt;,他们就可以将其转换为List&lt;T&gt; 吗? IReadOnlyCollection&lt;T&gt; 中没有任何内容明确说明它们不能转换为 List&lt;T&gt; 或对象是不可变的。虽然返回 ReadOnlyCollection&lt;T&gt; 略有不同。
  • But won't the caller assume that since List&lt;T&gt; implements IReadOnlyCollection&lt;T&gt; they can just cast it to List&lt;T&gt; 他们为什么会这么认为? SqlConnection 实现了IDisposable,但我不认为我收到的每个IDisposable 都可以转换为to SqlConnection
  • @mjwills 是对的。您要么需要调查您正在调用的函数的源代码,要么使用调试器检查您返回的对象的类型,以了解您是否可以将其转换为List。所以我会说调用者绝对不会认为他能够将IReadOnlyCollection 转换为List
  • @mjwills,你是对的。但是你假设你收到的所有IDisposible 东西都是一次性对象(不管它们实现了什么方法),因为名字是这样说的。对于IReadOnlyCollection,情况并非如此。顾名思义,任何实现此接口的类都将产生某种形式的只读对象。但事实并非如此。我认为我最大的抱怨是接口名称(以及它的 MSDN 描述)没有正确反映它的作用或当时的用途。
  • The name implies that whatever class implements this interface will result in some form of an object that's read only. 这是一种可能的解读方式,是的。 stackoverflow.com/a/15262988/34092 是另一种阅读方式(它讲的是IReadOnlyList,但原理是一样的)。
猜你喜欢
  • 2014-09-12
  • 2013-06-28
  • 2017-06-10
  • 1970-01-01
  • 2012-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多