Contra/Co 假定基本接口继承的泛型类型列表的变体
Contra/Co Variance for a list of generic types assuming base interface inheritance
在 .NET 中,我使用泛型进行了三级继承,其模式如下:
public interface IManager<TEvent> where TEvent : IEvent
{
void Foo( TEvent mySpecificEvent );
//...
}
class Manager<TEvent> : IManager<TEvent> where TEvent : IEvent
class NumericManager<TEvent> : Manager<TEvent> where TEvent : IEvent
class SpecificNumericManager : NumericManager<ISpecificNumericEvent>
这样特定的管理器可以处理特定的类型,希望我可以将几种特定的类型保存在一个列表中,并告诉他们对他们的特定事件进行 运行 Foo(TEvent mySpecificEvent)
操作作为只要事件继承自 IEvent
一切正常,但我尝试将我的 SpecificNumericManager
视为 IManager<IEvent>
以将其添加到 List<IManager<IEvent>>
我得到一个 运行 时间错误:
"Unable to cast object of type SpecificNumericManager
to type IManager<IEvent>
"
请注意,我的代码在 VB 中,但我已将其翻译成 C#,希望得到更多支持,因此,如果我犯了一些小错误,可能是因为它。
我已经尝试过,我可以成功地将 SpecificNumericManager<ISpecificNumericEvent>
转换为 IManager<ISpecificNumericEvent>
,但不能直接转换为 <Manager<IEvent>>
似乎泛型类型的逆变和协变出现在我第一次尝试找出我可以做些什么来修复它时,但我还没有发现诀窍是什么。
欢迎任何意见。谢谢!
你需要的是 IManager
是协变的。
interface IManager<out TEvent> where TEvent : IEvent
这样这个分配就合法了:
IManager<IEvent> manager = new Manager<IMoreSpecificEvent>();
从最基本的意义上讲,协变指定您不关心类型参数是否比此类分配的约束更派生。这意味着,除其他事项外,在协变实现中没有任何地方(所以在你的经理
您可以阅读有关 C# 中的协方差的更多信息 here。
编辑:
对,所以这个方法:
void Foo(TEvent mySpecificEvent);
破坏我们的小计划。现在你的 Manager
不是协变的,这是有充分理由的。让我们考虑一个列表:
List<IManager<IEvent>> list;
并说这个赋值是合法的(它不是,但让我们假装):
IManager<ISpecificNumericEvent> manager = new SpecificNumericManager();
list.Add(manager);
如果我们传递这个列表来对管理器做一些操作会发生什么?好吧,我们只知道里面的东西是 IManager<IEvent>
类型。这意味着我们应该能够将它 any IEvent
提供给 Foo
方法,对吗?
class BaseEvent : IEvent {}
foreach(var manager in list)
{
IEvent event = new BaseEvent();
manager.Foo(event);
}
但是由于我们之前已经为列表分配了一个 SpecificNumericManager
,我们正在尝试调用一个带有签名的方法:
void Foo(TEvent event) where TEvent : ISpecificNumericEvent
带有 BaseEvent
,未实现 ISpecificNumericEvent
。轰隆隆,类型系统在一场火热的爆炸中被摧毁,猫狗同居,集体歇斯底里
由于上面给出的原因,这个接口不能是协变的——C# 只是不允许早期绕过类型系统,不允许对 IManager<IEvent>
的协变赋值。出于同样的原因,将事件馈送到通用 IManager<IEvent>
s 的 for 循环将不是一个好主意,因为根本不知道特定管理器是否能够在编译时处理该特定事件。您需要考虑您真正想要通过该循环实现的目标。通常,当 covariance/contravariance 规则被打破时,那是因为你想做的事情真的没有意义。但是,如果您想对通用 IManager<IEvent>
列表中的类型做一些疯狂的事情,那么您将没有类型系统,并且必须使用反射来实现您的阴险目标。
@V0ldek 已经详细解释了为什么会这样。基本上,继承关系 TDerived : TBase
不会传播到 T<TDerived> : T<TBase>
.
一个可能的解决方案是声明一个非泛型基类型:
public interface IManager
{
void Foo(IEvent nonSpecificEvent);
//...
}
public interface IManager<TEvent> : IManager
where TEvent : IEvent
{
void Foo(TEvent mySpecificEvent);
//...
}
这允许您将列表声明为
List<IManager> _list;
... 与直接或间接派生自 IManager
的各种管理器兼容,是否通用。
这导致 Foo
.
的 2 个重载版本
当通过具有显式实现的定义接口以外的类型访问时,您可以隐藏成员:
class Manager<TEvent> : IManager<TEvent> where TEvent : IEvent
{
public void Foo(TEvent mySpecificEvent)
{
//...
}
// Explicit implementation
void IManager.Foo(IEvent nonSpecificEvent)
{
//...
}
}
在 VB 中,这可以通过将其声明为私有来完成
Private Sub Foo(nonSpecificEvent As IEvent) Implements IManager.Foo
Public Interface IManager
Sub Foo(nonSpecificEvent As IEvent)
End Interface
Public Interface IManager(Of TEvent As IEvent)
Inherits IManager
Overloads Sub Foo(mySpecificEvent As TEvent)
End Interface
Class Manager(Of TEvent As IEvent)
Implements IManager(Of TEvent)
Public Sub Foo(mySpecificEvent As TEvent) Implements IManager(Of TEvent).Foo
End Sub
' Hidden implementation
Private Sub Foo(nonSpecificEvent As IEvent) Implements IManager.Foo
End Sub
End Class
在 .NET 中,我使用泛型进行了三级继承,其模式如下:
public interface IManager<TEvent> where TEvent : IEvent
{
void Foo( TEvent mySpecificEvent );
//...
}
class Manager<TEvent> : IManager<TEvent> where TEvent : IEvent
class NumericManager<TEvent> : Manager<TEvent> where TEvent : IEvent
class SpecificNumericManager : NumericManager<ISpecificNumericEvent>
这样特定的管理器可以处理特定的类型,希望我可以将几种特定的类型保存在一个列表中,并告诉他们对他们的特定事件进行 运行 Foo(TEvent mySpecificEvent)
操作作为只要事件继承自 IEvent
一切正常,但我尝试将我的 SpecificNumericManager
视为 IManager<IEvent>
以将其添加到 List<IManager<IEvent>>
我得到一个 运行 时间错误:
"Unable to cast object of type SpecificNumericManager
to type IManager<IEvent>
"
请注意,我的代码在 VB 中,但我已将其翻译成 C#,希望得到更多支持,因此,如果我犯了一些小错误,可能是因为它。
我已经尝试过,我可以成功地将 SpecificNumericManager<ISpecificNumericEvent>
转换为 IManager<ISpecificNumericEvent>
,但不能直接转换为 <Manager<IEvent>>
似乎泛型类型的逆变和协变出现在我第一次尝试找出我可以做些什么来修复它时,但我还没有发现诀窍是什么。
欢迎任何意见。谢谢!
你需要的是 IManager
是协变的。
interface IManager<out TEvent> where TEvent : IEvent
这样这个分配就合法了:
IManager<IEvent> manager = new Manager<IMoreSpecificEvent>();
从最基本的意义上讲,协变指定您不关心类型参数是否比此类分配的约束更派生。这意味着,除其他事项外,在协变实现中没有任何地方(所以在你的经理 您可以阅读有关 C# 中的协方差的更多信息 here。
编辑:
对,所以这个方法:
void Foo(TEvent mySpecificEvent);
破坏我们的小计划。现在你的 Manager
不是协变的,这是有充分理由的。让我们考虑一个列表:
List<IManager<IEvent>> list;
并说这个赋值是合法的(它不是,但让我们假装):
IManager<ISpecificNumericEvent> manager = new SpecificNumericManager();
list.Add(manager);
如果我们传递这个列表来对管理器做一些操作会发生什么?好吧,我们只知道里面的东西是 IManager<IEvent>
类型。这意味着我们应该能够将它 any IEvent
提供给 Foo
方法,对吗?
class BaseEvent : IEvent {}
foreach(var manager in list)
{
IEvent event = new BaseEvent();
manager.Foo(event);
}
但是由于我们之前已经为列表分配了一个 SpecificNumericManager
,我们正在尝试调用一个带有签名的方法:
void Foo(TEvent event) where TEvent : ISpecificNumericEvent
带有 BaseEvent
,未实现 ISpecificNumericEvent
。轰隆隆,类型系统在一场火热的爆炸中被摧毁,猫狗同居,集体歇斯底里
由于上面给出的原因,这个接口不能是协变的——C# 只是不允许早期绕过类型系统,不允许对 IManager<IEvent>
的协变赋值。出于同样的原因,将事件馈送到通用 IManager<IEvent>
s 的 for 循环将不是一个好主意,因为根本不知道特定管理器是否能够在编译时处理该特定事件。您需要考虑您真正想要通过该循环实现的目标。通常,当 covariance/contravariance 规则被打破时,那是因为你想做的事情真的没有意义。但是,如果您想对通用 IManager<IEvent>
列表中的类型做一些疯狂的事情,那么您将没有类型系统,并且必须使用反射来实现您的阴险目标。
@V0ldek 已经详细解释了为什么会这样。基本上,继承关系 TDerived : TBase
不会传播到 T<TDerived> : T<TBase>
.
一个可能的解决方案是声明一个非泛型基类型:
public interface IManager
{
void Foo(IEvent nonSpecificEvent);
//...
}
public interface IManager<TEvent> : IManager
where TEvent : IEvent
{
void Foo(TEvent mySpecificEvent);
//...
}
这允许您将列表声明为
List<IManager> _list;
... 与直接或间接派生自 IManager
的各种管理器兼容,是否通用。
这导致 Foo
.
当通过具有显式实现的定义接口以外的类型访问时,您可以隐藏成员:
class Manager<TEvent> : IManager<TEvent> where TEvent : IEvent
{
public void Foo(TEvent mySpecificEvent)
{
//...
}
// Explicit implementation
void IManager.Foo(IEvent nonSpecificEvent)
{
//...
}
}
在 VB 中,这可以通过将其声明为私有来完成
Private Sub Foo(nonSpecificEvent As IEvent) Implements IManager.Foo
Public Interface IManager
Sub Foo(nonSpecificEvent As IEvent)
End Interface
Public Interface IManager(Of TEvent As IEvent)
Inherits IManager
Overloads Sub Foo(mySpecificEvent As TEvent)
End Interface
Class Manager(Of TEvent As IEvent)
Implements IManager(Of TEvent)
Public Sub Foo(mySpecificEvent As TEvent) Implements IManager(Of TEvent).Foo
End Sub
' Hidden implementation
Private Sub Foo(nonSpecificEvent As IEvent) Implements IManager.Foo
End Sub
End Class