Reputation: 13
I have a cascade of three abstract generic classes, as follows:
public abstract class SpawnTrigger<T>
public abstract class SpawnerConfig <T, S> where T : SpawnTrigger<S> {}
public abstract class Spawner<T, S> where T:SpawnerConfig<SpawnTrigger<S>, S>{}
I have three corresponding concrete classes. Each one inherits from one of the abstract generic classes, as follows:
public class OrbSpawnTrigger : SpawnTrigger<Orb>{}
public class OrbSpawnerConfig : SpawnerConfig<OrbSpawnTrigger, Orb>{}
public class OrbSpawner : Spawner<OrbSpawnerConfig, Orb>{}
For simplicity I have removed the bodies of these classes, above.
The compiler is complaining about OrbSpawner
. Specifically, it is complaining that:
The type 'OrbSpawnerConfig' must be convertible to 'SpawnerConfig<SpawnTrigger<Orb>,Orb>' in order to use it as parameter 'T' in the generic class 'Spawner<T,S>'
I cannot figure out why it is complaining. OrbSpawnerConfig
is a SpawnerConfig<SpawnTrigger<Orb>, Orb>
:
OrbSpawnerConfig
-> SpawnerConfig<OrbSpawnTrigger, Orb>
-> SpawnerConfig<SpawnTrigger<Orb>, Orb>
Does anyone understand this error? I am working in C# on a Unity project. Is there a peculiarity or limitation of C# that I am unaware of?
I should mention that the following produces no error - but it is not a solution because I want to specify a concrete class not the inherited generic abstract:
public class OrbSpawner : Spawner<SpawnerConfig<SpawnTrigger<Orb>, Orb>, Orb>
The error is easily reproducible with the following code:
namespace Test
{
public abstract class SpawnTrigger<T> {}
public abstract class SpawnerConfig<T, S> where T : SpawnTrigger<S> {}
public abstract class Spawner<T, S> where T : SpawnerConfig<SpawnTrigger<S>, S> {}
public class OrbSpawnTrigger : SpawnTrigger<Orb> { }
public class OrbSpawnerConfig : SpawnerConfig<OrbSpawnTrigger, Orb> { }
public class OrbSpawner : Spawner<OrbSpawnerConfig, Orb> {}
}
Here is the same code refactored to use simple class names (the error is in the definition of ConcreteC - that ConcreteB cannot be used as parameter V)
namespace Test
{
public abstract class A<T> {}
public abstract class B<T, U> where U : A<T> {}
public abstract class C<T, V> where V : B<T, A<T>> {}
public class ConcreteA : A<MyObj> {}
public class ConcreteB : B<MyObj, ConcreteA> {}
public class ConcreteC : C<MyObj, ConcreteB> {}
public class MyObj {}
}
Thanks to Dmitry Dovgopoly for providing a code solution that works, and to PetSerAI for explaining why my code has problems. As I understand it, I cannot use a derived ConcreteB to satisfy the constraint of the more general requirements of parameter V. And the solution is to use Interfaces with the out keyword, to create a Covariant interface. See covariant generic interface
Upvotes: 1
Views: 96
Reputation: 6301
In case your parameters can be covariant, you will fix this by adding interfaces
public interface ISpawnTrigger<out T>{}
public interface ISpawnerConfig<out T, S> where T : ISpawnTrigger<S>{}
public interface ISpawner<out T, S> where T : ISpawnerConfig<ISpawnTrigger<S>, S>{}
public abstract class SpawnTrigger<T> : ISpawnTrigger<T>{}
public abstract class SpawnerConfig<T, S> : ISpawnerConfig<T, S> where T : ISpawnTrigger<S>{}
public abstract class Spawner<T, S> : ISpawner<T, S> where T : ISpawnerConfig<ISpawnTrigger<S>, S>{}
public class OrbSpawnTrigger : SpawnTrigger<Orb>{}
public class OrbSpawnerConfig : SpawnerConfig<OrbSpawnTrigger, Orb>{}
public class OrbSpawner : Spawner<OrbSpawnerConfig, Orb>{}
public class Orb{}
Covariance and contravariance real world example
Upvotes: 1