c# - 推断或忽略嵌套 C# 接口中的嵌套泛型
问题描述
假设我们有一个IDevice
包含IDeviceTagBag
对象的容器,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
来访问每个设备,无论类型如何,但仅限于通用操作。
如果他们想使用特定的功能,他们可能会使用 say SpecificDeviceService
,但如果底层设备类型发生变化,他们将不得不更新他们的代码。
如果我希望它可以通过或访问,则SpecificDevice
需要实现:IDevice
DeviceService
SpecificDeviceService
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
解决方案
在我看来,第三个接口不需要两个泛型类型。你可以用一个 where 来解决它:
public interface IDeviceTag { }
public interface IDeviceTagBag<out TDeviceTag>
where TDeviceTag : IDeviceTag
{ }
public interface IDevice<TDeviceTagBag>
where TDeviceTagBag : IDeviceTagBag<IDeviceTag>
{ }
推荐阅读
- node.js - 在 nodejs 客户端/服务器中通过 TCP 发送查询结果的问题
- xml - 现有代码中的大写集成 (XSLT 1.0)
- javascript - ReactJs - 在 li 元素上的单击事件上,不会立即添加该类
- swift - SwiftUI Core Data Master/Detail
- ruby - Ruby 命名空间与在方法中定义并使用 attr 的模块类实例变量冲突:
- java - 如何让一个被发现为真的方法在下面显示另一个方法?爪哇
- c# - 如何对以下意外事件进行编程?
- c++ - C++ 中的异或加密
- c# - 使用 Blazor 服务器端组件的 MVC 应用程序中的防伪令牌验证
- rust - 为什么这里会出现 Rust 可变借用?