Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic constraints, where T : struct and where T : class

People also ask

What are the constraints in generics?

The where clause in a generic definition specifies constraints on the types that are used as arguments for type parameters in a generic type, method, delegate, or local function. Constraints can specify interfaces, base classes, or require a generic type to be a reference, value, or unmanaged type.

What does the T designation indicate in a generic class?

Generics appear in TypeScript code inside angle brackets, in the format < T > , where T represents a passed-in type. <T> can be read as a generic of type T .

Can generic classes be constrained?

Declaring those constraints means you can use the operations and method calls of the constraining type. If your generic class or method uses any operation on the generic members beyond simple assignment or calling any methods not supported by System. Object, you'll apply constraints to the type parameter.


Constraints are not part of the signature, but parameters are. And constraints in parameters are enforced during overload resolution.

So let's put the constraint in a parameter. It's ugly, but it works.

class RequireStruct<T> where T : struct { }
class RequireClass<T> where T : class { }

static void Foo<T>(T a, RequireStruct<T> ignore = null) where T : struct { } // 1
static void Foo<T>(T? a) where T : struct { } // 2
static void Foo<T>(T a, RequireClass<T> ignore = null) where T : class { } // 3

(better six years late than never?)


You cannot differentiate the type of method to call based only on the constraints, unfortunately.

So you need to define a method in a different class or with a different name instead.


Further to your comment on Marnix's answer, you can achieve what you want by using a bit of reflection.

In the example below, the unconstrained Foo<T> method uses reflection to farm out calls to the appropriate constrained method - either FooWithStruct<T> or FooWithClass<T>. For performance reasons we'll create and cache a strongly-typed delegate rather than using plain reflection every time the Foo<T> method is called.

int x = 42;
MyClass.Foo(x);    // displays "Non-Nullable Struct"

int? y = 123;
MyClass.Foo(y);    // displays "Nullable Struct"

string z = "Test";
MyClass.Foo(z);    // displays "Class"

// ...

public static class MyClass
{
    public static void Foo<T>(T? a) where T : struct
    {
        Console.WriteLine("Nullable Struct");
    }

    public static void Foo<T>(T a)
    {
        Type t = typeof(T);

        Delegate action;
        if (!FooDelegateCache.TryGetValue(t, out action))
        {
            MethodInfo mi = t.IsValueType ? FooWithStructInfo : FooWithClassInfo;
            action = Delegate.CreateDelegate(typeof(Action<T>), mi.MakeGenericMethod(t));
            FooDelegateCache.Add(t, action);
        }
        ((Action<T>)action)(a);
    }

    private static void FooWithStruct<T>(T a) where T : struct
    {
        Console.WriteLine("Non-Nullable Struct");
    }

    private static void FooWithClass<T>(T a) where T : class
    {
        Console.WriteLine("Class");
    }

    private static readonly MethodInfo FooWithStructInfo = typeof(MyClass).GetMethod("FooWithStruct", BindingFlags.NonPublic | BindingFlags.Static);
    private static readonly MethodInfo FooWithClassInfo = typeof(MyClass).GetMethod("FooWithClass", BindingFlags.NonPublic | BindingFlags.Static);
    private static readonly Dictionary<Type, Delegate> FooDelegateCache = new Dictionary<Type, Delegate>();
}

(Note that this example is not threadsafe. If you require thread-safety then you'll either need to use some sort of locking around all access to the cache dictionary, or -- if you're able to target .NET4 -- use ConcurrentDictionary<K,V> instead.)


Drop the struct contraint on the first method. If you need to differentiate between value types and classes you can use the type of the argument to do so.

      static void Foo( T? a ) where T : struct
      {
         // nullable stuff here
      }

      static void Foo( T a )
      {
         if( a is ValueType )
         {
            // ValueType stuff here
         }
         else
         {
            // class stuff
         }
      }