Hello denizens of stack overflow, I'm having an issue (perhaps with understanding?) regarding polymorphism and generics. I want to be able to define a "system" that contains a "component". I also want to be able to extend systems and components to be able to have greater functionality.
I currently have two base classes, Component, and I/A_ComponentSystem (For the sake of brevity, I'm not going to be showing any actual code, just definitions):
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 { }
Below is an example component/system created:
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 { }
Below are some excerpts of actually trying to use the code/morph between the different objects (please note that the code isn't meant to be used in this way, but I'm writing unit tests to ensure compatibility between layers)
// 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;
I have another interface/class, SystemManager, which is defined thusly:
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>;
}
Now, the specific block of code that I have here will throw an error as well:
//*** blows up here as well ***(2)
ISystemManager sysMan = new SystemManager(); // defined elsewhere
sysMan.AddNewComponentToEntity<Tag, ITagSystem>(entity);
As far as the errors that I receive, error (1) is:
Cannot convert type 'TagSystem' to 'AComponentSystem<Tag>'
Error (2) is below:
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>'.
Now, as far as my question goes, it is thusly:
TagSystem
to AComponentSystem<Tag>
? This seems like a valid morph. ITagSystem
not converting to IComponentSystem<Tag>
? It appears that Tag should still conform to ITag, which is supported. Thank you to anyone for reading this and assisting me.
Note: Yes, this is for an EntityFramework driven game engine. I'm building it mainly as an exercise for myself, and so I can quickly spin up 3d projects for myself. Yes, I've built a few game projects before, no I'm not interested in "finishing" a game, I'm just tinkering and having fun.
Without a simpler and yet more-complete code example, it's impossible to provide specific advice in your specific scenario. However, the basic problem is that the types are indeed not convertible, just as the compiler says.
- Why can I not convert
TagSystem
toAComponentSystem<Tag>
? This seems like a valid morph.
TagSystem
doesn't inherit AComponentSystem<Tag>
. It inherits AComponentSystem<ITag>
. These two types are not actually the same. Just because Tag
inherits/implements ITag
, that does not mean that AComponentSystem<Tag>
automatically inherits/implements AComponentSystem<ITag>
. If it did, then that would mean that a method or property of AComponentSystem<Tag>
that normally would return a value of type Tag
, could wind up being used in a situation where a Tag
value is expected, but some other implementation of ITag
is actually returned. This is because you would be able to cast to AComponentSystem<Tag>
, and then use that reference to return the non- Tag
implementation of ITag
, to some code that only wanted Tag
.
This is bad for what I hope are obvious reasons, so the compiler doesn't allow you to do that.
- Why is
ITagSystem
not converting toIComponentSystem<Tag>
? It appears that Tag should still conform to ITag, which is supported.
Without a good Minimal, Complete, and Verifiable code example , it's difficult to answer this part of your question, as the types you've shown don't appear consistent with the code you've shown. ITagSystem
is declared as inheriting AComponentSystem<ITag>
, which in turn implements only IComponentSystem
, not IComponentSystem<TComponent>
.
So based on the code shown, there's no reason even naively to think that the conversion could work. But let's assume for a moment there's a typo in the type declarations you've shown. Then the answer is basically the same as above: implementing IComponentSystem<ITag>
is not the same as implementing IComponentSystem<Tag>
.
- Is there any way I could change my hierarchy while preserving my need for that many layers of abstraction?
Possibly. It depends on what these types actually do. Since C# 4, we've been able to specify generic type parameters on interfaces with covariance and contravariance. With a type parameter thus restricted, and interface members to match, the interface then can support specific casting scenarios like you're trying to do.
But note that this only works when the interface members really are compatible with such conversions. The compiler still won't let you do anything unsafe.
There are a lot of questions on Stack Overflow already discussing this. Technically your question could even be considered a duplicate of those. I hope the above addresses your immediate concerns, and gives you enough information to do more research and see if generic interface variance will work in your situation. If you need more help, I recommend you post a new question and make sure to include a good MCVE that clearly illustrates your question in the simplest way possible.
TagSystem
distantly inherits AComponentSystem<ITag>
, but you are trying to convert it to AComponentSystem<Tag>
. (Note the lack of an "I" in the generic type.) Those two generic types of AComponentSystem<>
are completely different, and you cannot freely cast between the two. Tag
is a child of ITag
doesn't mean that IComponentSystem<Tag>
is a child of IComponentSystem<ITag>
. To give a better example of my first point, take for example a common generic type: the List. If generics followed the same inheritance rules as normal classes, then List<Car>
would be a subtype of List<Vehicle>
. But the difference between the two is that the first list can only hold cars, while the second list can hold any vehicle . So if these lists were parent and child, you would be able to do the following:
List<Car> cars = new List<Car>();
List<Vehicle> vehicles = (List<Vehicle>)cars;
vehicles.Add(new Truck());
You see the problem? The general rules of inheritance just allowed us to add a non-Car object to out list of cars. Or they would, provided that is a legal cast, which it isn't. In reality, List<Car>
and List<Vehicle>
are not related in any way, but are actually completely separate classes with no direct relation whatsoever.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.