Search code examples
c#genericsgeneric-constraints

How to specify generic constraint for unbound generic type?


There is a generic class C1<T> that depends on some type T which is required to be derived from some class P1, cf. example below.

Now the non generic class P1 is to be replaced with a generic class P2<TE> (where TE is expected to be an Enum). The example below shows a few tries to define a corresponding class C2x with various errors or drawbacks, as commented.

How can one specify a class C2x<T> without introducing a new (redundant) generic parameter (such as TE in C25) to it that accepts for T any class derived from P2<TE> for any TE?

Example:

using System;

public enum E1 {}
public enum E2 {}

public class P1 {}
public class P2<TE> where TE : Enum {}

public class C1<T> where T : P1 {} // Ok, but P1 needs to be changed to some generic P2.
public class C21<T> where T : P2 {} // Using the genric Type 'P2<TE>' requires 1 type arguments
public class C22<T> where T : P2<> {} // Unexpected use of an unbound generic name
public class C23<T> where T : P2<TE> {} // The type or namespace name 'TE' could not be found (are you missing a using directive or an assembly reference?)
public class C24<T> where T : P2<TE> where TE : Enum {} // Additionally: 'D24<T>' does not define type parameter 'TE'
public class C25<T, TE> where T : P2<TE> where TE : Enum {} // Accepted but redundant dependency of D25 on TE.

public class Program
{
    C25<P2<E1>, E1> c1; // Redundant specification of E1.
    C25<P2<E1>, E2> c2; // The type 'P2<E1>' cannot be used as type parameter 'T' in the generic type or method 'C25<T, TE>'. There is no implicit reference conversion from 'P2<E1>' to 'P2<E2>'.
}

Solution

  • Your options are either to introduce a interface for P2<TE> that is used for the generic restriction, or to live with the redundant specification.

    In some cases you can write helper methods that can avoid the need to manually specify generic type arguments, for example:

    public static class Extensions
    {
        public static C25<T, TE> CreateC25<T, TE>(T t) where T : P2<TE> where TE : Enum => new();
    }
    

    You should probably also consider if you need to use generics in the first place. In some cases it is better to just use a regular reference. Generic restrictions can be useful for things like "generic math" where it can allow the compiler to generate optimized code, but there are some limitations to the type system. So while it would be nice to be able to write something like where T : P2<>, it is just not currently possible. Possible reasons is that writing compilers is difficult, you need to be careful when designing your language to not introduce ambiguity, and you need to keep compilation times from spiraling out of control.