I would like to differentiate between following cases:
- A plain value type (e.g.
int
) - A nullable value type (e.g.
int?
) - 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)?
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.
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 - eitherFooWithStruct<T>
orFooWithClass<T>
. For performance reasons we'll create and cache a strongly-typed delegate rather than using plain reflection every time theFoo<T>
method is called.(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.)Thankfully this kind of messing around is required less from C# version 7.3
See Whats new in C# 7.3 - Its not very explicit, but it now appears to use the 'where' arguments to some extent during overload resolution.
Also see Selecting C# Version in your visual studio project
It will still see clashes with the following
But will correctly resolve
Amplifying my comment to LukeH, a useful pattern if one will need to use Reflection to invoke different actions based upon a type parameter (as distinct from the type of an object instance) is to create a private generic static class something like the following (this exact code is untested, but I've done this sort of thing before):
Note that Reflection will throw an exception if one attempts to create a delegate for
ActionForOneKindOfThing<TT>(TT param)
whenTT
does not comply with that method's constraints. Because the system validated the type ofTT
when the delegate was created, one can safely invoketheAction
without further type-checking. Note also that if outside code does:only the first call will require any Reflection. Subsequent calls will simply invoke the delegate directly.
If you don't need generic parameters and just want to differentiate between these 3 cases at compile time you can use following code.
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.