我有一个List<GKComponentSystem<GKComponent>>
和一个通用的Add<T>()
方法,其中包含T
是GKComponent
的约束。
为什么我无法将GKComponentSystem<T>
实例添加到我的列表中?请参阅下面截取的代码:
List<GKComponentSystem<GKComponent>> _systems = new List<GKComponentSystem<GKComponent>>();
public void AddSystem<T>(int position = -1) where T : GKComponent
{
var system = new GKComponentSystem<T>();
_systems.Add(system);
}
错误:
参数 #1 无法转换 GameplayKit.GKComponentSystem
表达式 键入 GameplayKit.GKComponentSystem
那是在_systems.Add(system)
行中.
我以为我知道 C#,但这是我很高兴拥有 StackOverflow 的情况之一 - 我到底不明白这里是什么?
system
是GKComponentSystem<T>
,T
必须是GKComponent
,所以system
是一个GKComponentSystem<GKComponent>
,我应该能够将其添加到我的列表中。
这是GKComponentSystem
:
public class GKComponentSystem<T> : NSObject where T : GKComponent
它的T
也是一个GKComponent
...
这是关于逆变的吗(我绝对必须了解更多的主题)?
下面是一个更简单的示例:
class Parent
{
}
class Child : Parent
{
}
class GenericClass<T>
{
}
Parent p;
p = new Child(); // A child inherits from Parent, so this is allowed.
GenericClass<Parent> gp;
gp = new GenericClass<Child>(); // Not allowed! GenericClass<Child> does not inherit from GenericClass<Parent>
在您的示例中,T
继承自GKComponent
的事实不会转换为GKComponentSystem<T>
可以转换为GKComponentSystem<GKComponent>
的规则。
因此,现在让我们将其应用于列表。
List<Parent> l = new List<Parent>();
l.Add(new Child()); // A child can be converted to a Parent, this is OK
List<GenericClass<Parent>> gl = new List<GenericClass<Parent>>();
gl.Add(new GenericClass<Child>()); // A GenericClass<Child> does not convert to GenericClass<Parent>, so this is not allowed.
如果你真的希望它工作,你可以定义一个泛型接口。这些允许您使用out
指定通用参数,如下所示:
interface IGenericClass<out T>
{
}
class GenericClass<T> : IGenericClass<T>
{
}
IGenericClass<Child> gcChild = new GenericClass<Child>();
IGenericClass<Parent> gcParent = gcChild; // This is allowed!
var l = new List<IGenericClass<Parent>>();
l.Add(new GenericClass<Child>()); // Also allowed
因此,将其应用于您的示例:
interface IGKComponentSystem<out T>
{
}
class GKComponentSystem<T> : IGKComponentSystem
{
}
List<IGKComponentSystem<GKComponent>> _systems = new List<IGKComponentSystem<GKComponent>();
// Should work from there...
public void AddSystem<T>(int position = -1) where T : GKComponent
{
var system = new GKComponentSystem<T>();
_systems.Add(system);
}
将自定义类型替换为 BCL 类型:
List<List<object>> _systems = new List<List<object>>();
public void AddSystem<T>(int position = -1) where T : class
{
var system = new List<T>();
_systems.Add(system);
}
现在,假设这是有效的,我要打电话给AddSystem<string>();
.这将创建一个List<string>
,并将其添加到_systems
中。
现在,假设我打电话给_systems[0].Add(1);
.没有什么可以阻止这一点。_systems
是一个List<List<object>>
,因此_systems[0]
是一个List<object>
,因此它的Add
方法接受任何对象。
但我创造的是List<string>
,而不是List<object>
。应该无法向其添加int
。
编译器可以拒绝此操作的唯一方法是使List<string>
到List<object>
的转换无效。或者在您的情况下,GKComponentSystem<GKComponent>
转换GKComponentSystem<T>
。
这是关于逆变的吗(我绝对必须了解更多的主题)?
有点。接口和委托类型可以指定进行一些额外的保证。IEnumerable<string>
toIEnumerable<object>
转换是安全的,并且允许包含相应注释的类型。但是它不适用于类类型,并且可以在这样的界面中放置的内容有点限制,任何可能不安全的东西都是不允许的。
如果你想放在GKComponentSystem
中的东西只允许读取,这意味着它没有List
那样的问题,你可以创建一个IGKComponentSystem<T>
接口,并存储一个List<IGKComponentSystem<GKComponent>>
,并添加一个IGKComponentSystem<T>
实例,没有问题。
如果你想放入GKComponentSystem
的内容也允许写入(例如Add
方法),那么转换本质上是不安全的,你需要重新考虑你的设计。