首页 > 解决方案 > 推断或忽略嵌套 C# 接口中的嵌套泛型

问题描述

假设我们有一个IDevice包含IDeviceTagBag对象的容器,IDeviceTagBag它本身就是一个IDeviceTag对象容器。

现在我希望它们成为泛型类,同时保持上述约束。

Java中,我会执行以下操作:

public interface IDeviceTag {}

public interface IDeviceTagBag<TDeviceTag extends IDeviceTag> {}

public interface IDevice<TDeviceTagBag extends IDeviceTagBag<?>> {}

编写返回IDeviceJava解决方案)的方法现在看起来像这样:

public class DeviceService
{
    // Compiler will infer the following covariant chain:
    // -> ? extends IDeviceTagBag -> ? extends IDeviceTag
    public IDevice<?> Get(string name)
    {
        return null;
    }

    // Or the invariant alternative:
    // -> IDeviceTagBag -> IDeviceTag
    public IDevice<IDeviceTagBag<IDeviceTag>> GetInvariant(string name)
    {
        return null;
    }
}

我尝试使用C# (不变或协变)来实现相同的目标,但我最终得到了下面的解决方案,感觉就像一个大样板:

// OK !
public interface IDeviceTag {}

// OK !
public interface IDeviceTagBag<TDeviceTag> where TDeviceTag : IDeviceTag {}

// Ouch, no substitute for wildcard "<?>"
public interface IDevice<TDeviceTagBag, TDeviceTag>
    where TDeviceTagBag : IDeviceTagBag<TDeviceTag>
    where TDeviceTag    : IDeviceTag
{}

编写返回IDeviceC#解决方案)的方法如下所示:

public class DeviceService
{
    // So much to replace "<?>"
    public IDevice<IDeviceTagBag<IDeviceTag>, IDeviceTag> Get(string name)
    {
        return null;
    }
}

实际上,我的应用程序中有第四个嵌套的通用接口,它变得令人讨厌。

我是否遗漏了一些可以简化它的东西,比如在Java解决方案中使用<?>?

找到这些帖子后...

...我担心对此我无能为力。

还是我过度设计了这件事?毕竟,如果我删除了where约束,我就不再有这个样板问题;但开发人员可能会实现IDevice其他东西IDeviceTagBag...


答案的结论 (19.10.2019)

两个答案都很好,但我只能接受一个……所以我接受最能复制我的 Java 解决方案的那个。:(


附录:为什么我需要我的接口是通用的

我构建了一个库,提供与各种设备类型的通信功能。他们所有人的共同行动是接收和发送消息。然后,根据设备类型,还有其他功能。

使用该库的开发人员可以使用通用DeviceService来访问每个设备,无论类型如何,但仅限于通用操作。

如果他们想使用特定的功能,他们可能会使用 say SpecificDeviceService,但如果底层设备类型发生变化,他们将不得不更新他们的代码。

如果我希望它可以通过或访问,则SpecificDevice需要实现:IDeviceDeviceServiceSpecificDeviceService

public interface IDevice
{
    IDeviceTagBag Tags
    {
        get;
    }

    // ...
}

public interface ISpecificDevice : IDevice
{
    // Problem:
    // - "Tags" still return "IDeviceTagBag" here and not "ISpecificDeviceTagBag"
    // - End users will have to do explicit casts
}

为了防止评论中所说的“问题”,一个解决方案是使用泛型。

// New problem: "TDeviceTagBag" may be something else than "IDeviceTagBag"
public interface IDevice<TDeviceTagBag>
{
    TDeviceTagBag Tags
    {
        get;
    }

    // ...
}

public interface ISpecificDevice : IDevice<ISpecificDeviceTagBag>
{
    // First problem solved: "Tags" return "ISpecificDeviceTagBag"
}

但是,当使用where条件约束泛型类型来解决上面的新问题时,我得到了上面问题中解释的“样板”代码,因为我总共有 4 层:

IDeviceService -> IDevice -> IDeviceTagBag -> IDeviceTag

标签: c#generics

解决方案


在我看来,第三个接口不需要两个泛型类型。你可以用一个 where 来解决它:

public interface IDeviceTag { }

public interface IDeviceTagBag<out TDeviceTag>
    where TDeviceTag : IDeviceTag
{ }

public interface IDevice<TDeviceTagBag>
    where TDeviceTagBag : IDeviceTagBag<IDeviceTag>
{ }

推荐阅读