【问题标题】:Infer or ignore nested generics in nested C# interfaces推断或忽略嵌套 C# 接口中的嵌套泛型
【发布时间】:2019-10-15 14:59:40
【问题描述】:

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

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

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

public interface IDeviceTag {}

public interface IDeviceTagBag<TDeviceTag extends IDeviceTag> {}

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

编写返回 IDevice 的方法(Java 解决方案)现在如下所示:

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
{}

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

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

我的应用程序中实际上有第四个嵌套的通用接口,它变得很糟糕。

我是否遗漏了一些可以简化这一点的东西,比如在 Java 解决方案中使用&lt;?&gt;

找到这些帖子后...

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

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


答案的结论 (19.10.2019)

  • 如果您的接口是只读的,您可以使您的接口协变。请参阅@Alpha75 答案。

  • 如果您的接口不是只读的,您可以创建非泛型接口,它们是泛型接口的“兄弟”,以替代 Java &lt;?&gt; 通配符泛型。请参阅@canton7 的答案。

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


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

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

使用该库的开发者可以使用通用的DeviceService 来访问所有类型的设备,但仅限于通用操作。

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

如果我希望通过DeviceServiceSpecificDeviceService 访问SpecificDevice,则需要实现IDevice

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
}

为了防止出现cmets中所说的“问题”,一个解决办法就是使用泛型。

// 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

【问题讨论】:

  • 没有界面成员很难回答你的问题。泛型类型参数用于接口和类中的方法和属性声明,因此答案取决于接口本身中定义的成员。在不使用泛型的情况下,您可以简单地强制开发人员在方法参数或返回类型中使用 IDevice。
  • @RobertoFerraris 感谢您的评论。我添加了一个附录,解释了为什么我需要通用接口。

标签: c# generics


【解决方案1】:

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

public interface IDeviceTag { }

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

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

【讨论】:

  • 问题是,如果IDeviceTagBag没有被标记为协变,那么有人将无法做到class DeviceTag : IDeviceTag { } class DeviceTagBag : IDeviceTagBag&lt;DeviceTag&gt; { } class Device : IDevice&lt;DeviceTagBag&gt; { }SharpLab
  • 谢谢,这是正确的。我会将其标记为协变的。
  • 感谢您的回答!我让我的接口协变,它看起来不错,因为对我的用户强制协变对我来说不是问题。但是双变量解决方案似乎是不可能的。我是否在声明站点差异模型中发现了“缺陷”? :p
  • @kagmole 只是一个仅供参考,双变量将是具有 &lt;in out TDeviceTag&gt; 行为的东西(这确实是不可能的)。仅&lt;TDeviceTag&gt; 的术语称为“不变”
  • @ScottChamberlain 你是对的,我会更新我的问题/cmets 来解决这个问题。
【解决方案2】:

如果我对您的问题的理解是正确的,那么 C# 方式将是:

public interface IDeviceTag {}

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

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

也就是说,您必须手动拆分 IDeviceTagBag 中依赖于 TDeviceTag 的部分和不依赖于 TDeviceTag 的部分。然后你在非泛型接口IDeviceTagBag上暴露不依赖TDeviceTag的部分。

您的 Java 解决方案有:

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

在我对 Java 通配符(有点生疏)的理解中,这意味着您将无法将任何 TDeviceTag 成员视为除 object 之外的任何其他成员,这大致相当于只能访问成员非泛型IDeviceTagBag

【讨论】:

  • 感谢您的回答!但在 Java 中,IDeviceTagBag&lt;?&gt; 表示IDeviceTagBag“某物”。编译器将通过查看IDeviceTagBag 声明来检查“某事”,它会找到并推断IDeviceTag 或更准确地说,? extends IDeviceTag。所以不,它与使用原始类型不同。您的解决方案很好,但您将在我的问题的附录中解释“需要显式转换”问题。
  • 同一个问题有多种解决方法的乐趣!
猜你喜欢
  • 2020-08-15
  • 2013-03-19
  • 1970-01-01
  • 2023-02-09
  • 1970-01-01
  • 1970-01-01
  • 2020-12-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多