If one wishes to invoke a member generically regardless of whether it has a class constraint or a struct constraint, and have it invoke a method with a suitable constraint, one may define an interface IThingUser<T> to act upon any type T, along with one a class which implements it for value types and another which implements it for class types. Have a static class ThingUsers<T> with a static field TheUser of type IThingUser<T>, and have it populate that field with an instance of one of the above classes, and then ThingUsers<T>.theUser will be able to act upon any sort of T.
public static class GenTest93
{
public interface IThingUser<T> { void ActOnThing(T it); }
class StructUser<T> : IThingUser<T>, IThingUser<Nullable<T>> where T : struct
{
void IThingUser<T>.ActOnThing(T it) { System.Diagnostics.Debug.Print("Struct {0}", typeof(T)); }
void IThingUser<Nullable<T>>.ActOnThing(T? it) { System.Diagnostics.Debug.Print("Struct? {0}", typeof(T)); }
}
class ClassUser<T> : IThingUser<T> where T : class
{
void IThingUser<T>.ActOnThing(T it) { System.Diagnostics.Debug.Print("Class {0}", typeof(T)); }
}
static class ThingUsers<T>
{
class DefaultUser : IThingUser<T>
{
public void ActOnThing(T it)
{
Type t = typeof(T);
if (t.IsClass)
t = typeof(ClassUser<>).MakeGenericType(typeof(T));
else
{
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>))
t = t.GetGenericArguments()[0];
t = typeof(StructUser<>).MakeGenericType(t);
}
TheUser = (IThingUser<T>)Activator.CreateInstance(t);
TheUser.ActOnThing(it);
}
}
static IThingUser<T> TheUser = new DefaultUser();
public static void ActOnThing(T it) {TheUser.ActOnThing(it);}
}
public static void ActOnThing<T>(T it) { ThingUsers<T>.ActOnThing(it); }
public static void Test()
{
int? foo = 3;
ActOnThing(foo);
ActOnThing(5);
ActOnThing("George");
}
}
It's necessary to use Reflection to create an instance of StructUser<T> or ClassUser<T> if the compiler doesn't know that T satisfies the necessary constraint, but it's not too hard. After the first time ActOnThing<T>() is used for a particular T, ThingUsers<T>.TheUser will be set to an instance which can be used directly for any future calls toActOnThing(), so performance should be very good.
Note that if given a Nullable<T>, the method creates a StructUser<T> and casts it to IThingUser<Nullable<T>>, rather than trying to create a sometype<Nullable<T>>, since nullable types themselves don't satisfy any constraint.