2016-11-15 197 views
0

你好denizens堆栈溢出,我有一个问题(也许与理解?)关于多态性和泛型。我希望能够定义一个包含“组件”的“系统”。我也希望能够扩展系统和组件,以便能够拥有更强大的功能。C#泛型与多态性

我目前有两个基类,组件,以及I/A_ComponentSystem(为简便起见,我不会被显示任何实际的代码,只需定义):

public abstract class Component { } 

public interface IComponentSystem { } 
public interface IComponentSystem<TComponent> : IComponentSystem 
    where TComponent : Component { } 

// The following are what should should actually be inherited from 
public abstract class AComponentSystem : IComponentSystem { } 
public abstract class AComponentSystem<TComponent> : AComponentSystem 
    where TComponent : Component { } 

下面是一个例如组件/系统创建:

public abstract class ITag : Component { } // This is to allow generating the code in a different binary. Hard to explain in the question, I'll clarify if need be 
public class Tag : ITag { } 

public abstract class ITagSystem : AComponentSystem<ITag> { } 
public class TagSystem : ITagSystem { } 

下面是实际上是试图使用代码/不同对象之间的变形(请注意,该代码并不意味着以这种方式使用的一些摘录,但我m写单元测试以确保层之间的兼容性)

// This is the main system that will be passed around 
TagSystem tagSys = new TagSystem(); 

// This is OK 
ITagSystem ITagSys = (ITagSystem)ITagSys; 

// This is OK 
AComponentSystem A_TagSys = (AComponentSystem)tagSys; 

// This is OK 
AComponentSystem<ITag> ATag_TagSys = (AComponentSystem<ITag>)tagSys; 

// This is OK 
IComponentSystem I_TagSys = (IComponentSystem)tagSys; 

// This is OK 
IComponentSystem<ITag> ITag_TagSys = (IComponentSystem<ITag>)tagSys; 

// Even the following is OK (which is why I am confused) 
IComponentSystem<Tag> new_ITag_TagSys = (IComponentSystem<Tag>)tagSys; 

//***This is where it blows up*** (1) 
AComponentSystem<Tag> new_ATag_TagSys = (AComponentSystem<Tag>)tagSys; 

我有另一个接口/类,的SystemManager,它正是如此定义:

public interface ISystemManager 
{ 
    TComponent AddNewComponentToEntity<TComponent, TComponentSystem>(Entity e) // Please don't ask about Entity, it shouldn't be required for this snippet and I already feel like I've posted a lot) 
     where TComponent : Component, new() // Required for some reason or I get an error 
     where TComponentSystem : IComponentSystem<TComponent>; 
} 

现在,我这里特定的代码块将引发错误,以及:

//*** blows up here as well ***(2) 
ISystemManager sysMan = new SystemManager(); // defined elsewhere 
sysMan.AddNewComponentToEntity<Tag, ITagSystem>(entity); 

至于我收到错误,错误(1)是:

Cannot convert type 'TagSystem' to 'AComponentSystem<Tag>' 

错误(2)低于:

The type 'ITagSystem' cannot be used as type parameter 'TComponentSystem' in the generic type or method 'ISystemManager.AddNewComponentToEntity<TComponent,TComponentSystem>(Entity)'. There is no implicit reference conversion from 'ITagSystem' to 'IComponentSystem<Tag>'. 

现在,据我的问题去,它正是如此:

  1. 我为什么不能转换TagSystemAComponentSystem<Tag>?这看起来像一个有效的变形。
  2. 为什么ITagSystem不能转换为IComponentSystem<Tag>?看起来标签应该仍然符合ITag,这是支持的。
  3. 有没有什么办法可以改变我的层次结构,同时保留我对这么多层抽象的需求?

谢谢任何​​人阅读本文并协助我。

注意:是的,这是针对EntityFramework驱动的游戏引擎。我主要将它作为自己的练习来构建,因此我可以为自己快速启动3D项目。是的,我之前已经构建了一些游戏项目,不,我对“完成”游戏不感兴趣,我只是修修补补,玩得开心。

+0

啊对,泛型。我的道歉,我想不出正确的词汇,模板是第一个想到的。我会回去编辑帖子,实际上我根本没有太多的C++大师。 – Hondros

回答

2

如果没有更简单,更完整的代码示例,则无法在特定场景中提供具体的建议。但是,基本问题是,类型确实不可转换,就像编译器说的那样。

  1. 我为什么不能转换TagSystemAComponentSystem<Tag>?这看起来像一个有效的变形。

TagSystem不继承AComponentSystem<Tag>。它继承AComponentSystem<ITag>。这两种类型实际上并不相同。仅仅因为Tag继承/实现了ITag,这并不意味着AComponentSystem<Tag>会自动继承/实现AComponentSystem<ITag>。如果是这样,那么这意味着AComponentSystem<Tag>的方法或属性通常会返回Tag类型的值,可能会在预期值为Tag的情况下使用,但实际上会返回ITag的其他一些实现。这是因为您可以投射到AComponentSystem<Tag>,然后使用该参考返回非Tag实现ITag,某些代码只需要Tag

这对我所希望的显而易见的原因是不利的,所以编译器不允许你这样做。

  • 为什么ITagSystem不转换为IComponentSystem<Tag>?看起来标签应该仍然符合ITag,这是支持的。
  • 没有好的Minimal, Complete, and Verifiable code example,这是很难回答你的问题的一部分,作为类型,您已经证明不会出现与你所示的代码一致。 ITagSystem被宣告为继承AComponentSystem<ITag>,而这又只实现了IComponentSystem,而不是IComponentSystem<TComponent>

    因此,根据显示的代码,即使天真地认为转换可以工作也没有理由。但是让我们暂时假设在你显示的类型声明中有一个错字。那么答案基本上与上面相同:执行IComponentSystem<ITag>与执行IComponentSystem<Tag>不一样。

    1. 有没有什么办法可以改变我的层次结构,同时保留我对这么多层抽象的需求?

    可能。这取决于这些类型实际上做了什么。自C#4以来,我们已经能够在具有协变和逆变的接口上指定泛型类型参数。通过限制类型参数以及匹配接口成员,接口可以支持特定的投射场景,就像您正在尝试的那样。

    但请注意,只有在界面成员真正与此类转换兼容时,此功能才有效。编译器仍然不会让你做任何不安全的事情。

    Stack Overflow上有很多问题已经在讨论这个问题。从技术上讲,你的问题甚至可以被认为是重复的。我希望上述内容解决您的直接问题,并为您提供足够的信息以进行更多研究,并查看通用接口方差是否适用于您的情况。如果您需要更多帮助,我建议您发布一个新问题,并确保包含一个很好的MCVE,以最简单的方式清楚地说明您的问题。

    +0

    我只是想咬我的舌头,因为代码甚至不会编译,所以现实中没有办法发布这个MCVE版本。我唯一缺少的是实体课程,该课程声明我不会发布,因为它与问题无关。第一个失败代码上方的两行与您每(2)所述的内容相矛盾,因为它们确实转换为IComponentSystem 和IComponentSystem 。我想我可能能够做更多的转换,一旦它在IComponentSystem 。我将其标记为答案,因为它是最详细的。 – Hondros

    +0

    和预期的一样,此行解决了我的AComponentSystem问题:AComponentSystem new_ATag_TagSys =(AComponentSystem )ITag_TagSys; 。为什么这是好的超越了我,但我正在与它一起运行。只要它有效。我在想,我只需要重新定义我的ISystemManager.AddNewComponentToEntity实现不使用TComponent。 – Hondros

    1
    1. TagSystem远亲继承AComponentSystem<ITag>,而是你正试图将其转换为AComponentSystem<Tag>。(请注意泛型中缺少“I”。)AComponentSystem<>这两种通用类型是完全不同的,您不能在两者之间自由投射。
    2. 与第1点相同,只是因为TagITag的子女并不意味着IComponentSystem<Tag>IComponentSystem<ITag>的子女。
    3. 答案几乎肯定是肯定的,但完全取决于你将如何使用它。您可能还想问问自己,您是否真的需要这么多层抽象。

    举一个更好的例子,我的第一点,例如一个普通的泛型类型:列表。如果泛型遵循与普通类相同的继承规则,那么List<Car>将是List<Vehicle>的子类型。但两者的区别在于,第一个列表只能容纳汽车,而第二个列表可以容纳任何车辆。因此,如果这些列表是父母和子女,则可以执行以下操作:

    List<Car> cars = new List<Car>(); 
    List<Vehicle> vehicles = (List<Vehicle>)cars; 
    vehicles.Add(new Truck()); 
    

    您看到问题了吗?一般的继承规则只允许我们将一个非Car对象添加到汽车列表中。或者他们会,如果这是一个合法的演员,它不是。实际上,List<Car>List<Vehicle>没有任何关系,但实际上是完全分开的类,没有任何直接关系。