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

Pierre Arnaud picture Pierre Arnaud · Jun 4, 2010 · Viewed 58.5k times · Source

I would like to differentiate between following cases:

  1. A plain value type (e.g. int)
  2. A nullable value type (e.g. int?)
  3. A reference type (e.g. string) - optionally, I would not care if this mapped to (1) or (2) above

I have come up with the following code, which works fine for cases (1) and (2):

static void Foo<T>(T a) where T : struct { } // 1

static void Foo<T>(T? a) where T : struct { } // 2

However, if I try to detect case (3) like this, it does not compile:

static void Foo<T>(T a) where T : class { } // 3

The error message is Type 'X' already defines a member called 'Foo' with the same parameter types. Well, somehow I cannot make a difference between where T : struct and where T : class.

If I remove the third function (3), the following code does not compile either:

int x = 1;
int? y = 2;
string z = "a";

Foo (x); // OK, calls (1)
Foo (y); // OK, calls (2)
Foo (z); // error: the type 'string' must be a non-nullable value type ...

How can I get Foo(z) to compile, mapping it to one of the above functions (or a third one with another constraint, which I have not thought of)?

Answer

Alcaro picture Alcaro · Apr 21, 2016

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?)