在c#中返回集合时如何处理协方差?

Khr*_*nos 9 c# implementation interface covariance

我有回收收集和协方差的问题,我想知道是否有人有更好的解决方案.

场景是这样的:

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

interface IItem
{
    // some properties here
}

interface IResult
{
    IList<IItem> Items { get; }
}
Run Code Online (Sandbox Code Playgroud)

然后,将有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>();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

要创建这些对象,将有一个工厂接受版本并根据需要创建适当的具体类型.

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

Version1.Result result = new Version1.Result();
result.Items.Add(//something);
Run Code Online (Sandbox Code Playgroud)

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

IResult result = // create from factory
result.Items.Add(//something);
Run Code Online (Sandbox Code Playgroud)

但是,因为它已被转换为另一个列表,所以添加将不会执行任何操作,因为该项目不会添加回原始结果对象.

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

  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
}
Run Code Online (Sandbox Code Playgroud)

然后我就可以了

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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

我会试一试,看看这是否有用.

svi*_*ick 2

您面临的问题是因为想要公开一个类型(除其他外) \xe2\x80\x9cyou can add any IItemto me\xe2\x80\x9d,但它实际上会做的是 \xe2\x80\x9cYou只能将Items 添加到 me\xe2\x80\x9d。我认为最好的解决方案是实际曝光IList<Item>。使用它的代码无论如何都必须了解具体内容Item,如果它应该将它们添加到列表中。

\n\n

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

\n