If someone wants to call an element in the general case, regardless of whether it has a class constraint or a structure constraint and calls a method with a suitable constraint, you can define the IThingUser<T> interface to act on any type T , together with one class that implements it for value types, and another that implements it for class types. Have a static ThingUsers<T> class with a TheUser static field of type IThingUser<T> and fill this field with an instance of one of the above classes, and then ThingUsers<T>.theUser can act on any kind 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"); } }
You must use Reflection to create an instance of StructUser<T> or ClassUser<T> if the compiler does not know that T satisfies the required constraint, but it is not too complicated. After the first use, ActOnThing<T>() used for a specific T , ThingUsers<T>.TheUser will be set to an instance which can be used directly for any future calls to ActOnThing (), so performance should be very good.
Note that if Nullable<T> is specified, the method creates StructUser<T> and translates it to IThingUser<Nullable<T>> , rather than trying to create sometype<Nullable<T>> , since types themselves with a null value do not satisfy no restriction.
supercat
source share