Warm tip: This article is reproduced from stackoverflow.com, please click
c# unity3d generics

Resolve compiler error in type constraint of generic type

发布于 2020-03-27 10:31:12

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

Questioner
ColdPixel
Viewed
58
Dmitry Dovgopoly 2019-07-04 00:23

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