【问题标题】:How to deal with covariance when returning collection in c#?在c#中返回集合时如何处理协方差?
【发布时间】:2011-09-11 16:49:31
【问题描述】:

我在返回集合和协方差时遇到问题,我想知道是否有人有更好的解决方案。

场景是这样的:

我有 2 个版本的实现,我希望将版本实现完全分开(即使它们可能具有相同的逻辑)。在实现中,我想返回一个项目列表,因此在界面中,我将返回项目的接口列表。但是,在接口的实际实现中,我想返回item的具体对象。在代码中,它看起来像这样。

interface IItem
{
    // some properties here
}

interface IResult
{
    IList<IItem> Items { get; }
}

然后,将有 2 个命名空间具有这些接口的具体实现。例如,

命名空间版本 1

class Item : IItem

class Result : IResult
{
    public List<Item> Items
    {
        get { // get the list from somewhere }
    }

    IList<IItem> IResult.Items
    {
        get
        {
            // due to covariance, i have to convert it
            return this.Items.ToList<IItem>();
        }
    }
}

在命名空间Version2下会有另一个相同的实现。

要创建这些对象,将有一个工厂来获取版本并根据需要创建适当的具体类型。

如果调用者知道确切的版本并执行以下操作,则代码可以正常工作

Version1.Result result = new Version1.Result();
result.Items.Add(//something);

但是,我希望用户能够做这样的事情。

IResult result = // create from factory
result.Items.Add(//something);

但是,由于它已被转换为另一个列表,因此添加不会做任何事情,因为该项目不会被添加回原始结果对象。

我可以想到一些解决方案,例如:

  1. 我可以同步这两个列表,但这似乎是额外的工作
  2. 返回 IEnumerable 而不是 IList 并添加创建/删除集合的方法
  3. 创建一个采用 TConcrete 和 TInterface 的自定义集合

我理解为什么会发生这种情况(由于类型安全和所有),但我认为没有一种解决方法看起来很优雅。有人有更好的解决方案或建议吗?

提前致谢!

更新

想了很多之后,我觉得我可以做到以下几点:

public interface ICustomCollection<TInterface> : ICollection<TInterface>
{
}

public class CustomCollection<TConcrete, TInterface> : ICustomCollection<TInterface> where TConcrete : class, TInterface
{
    public void Add(TConcrete item)
    {
        // do add
    }

    void ICustomCollection<TInterface>.Add(TInterface item)
    {
        // validate that item is TConcrete and add to the collection.
        // otherwise throw exception indicating that the add is not allowed due to incompatible type
    }

    // rest of the implementation
}

那么我可以拥有

interface IResult
{
    ICustomCollection<IItem> Items { get; }
}

then for implementation, I will have

class Result : IResult
{
    public CustomCollection<Item, IItem> Items { get; }

    ICustomCollection<TItem> IResult.Items
    {
        get { return this.Items; }
    }
}

这样,如果调用者正在访问 Result 类,它将通过已经是 TConcrete 的 CustomCollection.Add(TConcrete item)。如果调用者通过 IResult 接口访问,它将通过 customCollection.Add(TInterface item) 并进行验证并确保类型实际上是 TConcrete。

我会试一试,看看是否可行。

【问题讨论】:

  • 我认为使用选项 #2 将需要最少的代码量。它还会减少界面的表面积,因为实现不负责完整的 IList 支持,而调用者可能不需要。
  • 你的解决方案看起来不错,但为什么你直接使用ICustomCollection&lt;T&gt;而不是ICollection&lt;T&gt;
  • 我完全可以。唯一的原因是我有一些专门用于收集的方法,这些方法不适用于需要通过内部代码访问的常规 ICollection,我忘了提及。

标签: c# interface implementation covariance


【解决方案1】:

您面临的问题是因为想要公开一个类型(除其他外)“您可以向我添加任何 IItem”,但它实际上会做的是“您只能添加 Items对我来说”。我认为最好的解决方案是实际公开IList&lt;Item&gt;。无论如何,使用它的代码必须知道具体的Item,如果它应该将它们添加到列表中。

但如果你真的想这样做,我认为最干净的解决方案是 3.,如果我理解正确的话。它将成为IList&lt;TConcrete&gt; 的包装器,并将实现IList&lt;TInterface&gt;。如果您尝试在其中放入不是TConcrete 的内容,则会引发异常。

【讨论】:

    【解决方案2】:

    +1 对 Brent 的评论:返回不可修改的集合并提供类的修改方法。

    只是重申为什么你不能让 1 以可解释的方式工作:

    如果您尝试添加到仅实现 IItem 但不是类型(或派生自)Item 的 List&lt;Item&gt; 元素,您将无法在列表中存储此新项目。结果接口的行为将非常不一致 - 可以很好地添加一些实现 IItem 的元素,sime 会失败,并且当您将实现更改为 version2 时,行为也会发生变化。

    简单的解决方法是将IList&lt;IItem&gt; 存储在List&lt;Item&gt; 中,但直接公开集合需要仔细考虑。

    【讨论】:

      【解决方案3】:

      问题在于 Microsoft 使用一个接口 IList 来描述可以附加的集合和不能附加的集合。虽然理论上可以让一个类以可变方式实现 IList 并以不可变方式实现 IList(前一个接口的 ReadOnly 属性将返回 false,而后者将返回 true),但没有办法一个类来指定它以一种方式实现 IList,而 IList where cat:T 以另一种方式实现。我希望 Microsoft 已经让 IList List 实现 IReadableByIndex、IWritableByIndex、IReadWriteByIndex、IAppendable 和 ICountable,因为它们允许协变和逆变,但他们没有。自己实现这些接口并为它们定义包装器可能会有所帮助,具体取决于协方差的帮助程度。

      【讨论】:

        猜你喜欢
        • 2012-06-29
        • 2012-03-03
        • 2014-10-19
        • 2011-04-15
        • 2017-05-15
        • 1970-01-01
        • 1970-01-01
        • 2022-01-01
        相关资源
        最近更新 更多