【发布时间】: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 解决方案中使用<?>?
找到这些帖子后...
...我担心对此我无能为力。
还是我过度设计了这个东西?毕竟,如果我删除 where 约束,我不再有这个样板问题;但开发人员可能会实现 IDevice 以外的 IDeviceTagBag...
答案的结论 (19.10.2019)
如果您的接口是只读的,您可以使您的接口协变。请参阅@Alpha75 答案。
如果您的接口不是只读的,您可以创建非泛型接口,它们是泛型接口的“兄弟”,以替代 Java
<?>通配符泛型。请参阅@canton7 的答案。
两个答案都很好,但我只能接受一个……所以我接受最能复制我的 Java 解决方案的一个。 :(
附录:为什么我需要通用接口
我构建了一个库,该库提供与各种设备类型的通信功能。他们所有人的共同行动是接收和发送消息。然后,根据设备类型,还有其他功能。
使用该库的开发者可以使用通用的DeviceService 来访问所有类型的设备,但仅限于通用操作。
如果他们想使用特定功能,他们可能会使用SpecificDeviceService,但如果底层设备类型发生变化,他们将不得不更新他们的代码。
如果我希望通过DeviceService 或SpecificDeviceService 访问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 感谢您的评论。我添加了一个附录,解释了为什么我需要通用接口。