General contraindications to method overloads

I have an interface with some generic methods, and I wanted to implement a method with overloads to either accept an instance of the class or its PK value (which is either int or GUID, but changes).

I added to methods like these examples:

void DoSomething<TKey>(TKey key) where TKey: struct; void DoSomething<TModel>(TModel model) where TModel : class; 

The name of the DoSomething method on the second of them is highlighted, and the error

The type 'ISomeStuff' already defines a member called DoSomething with the same parameter types.

I am surprised by this, since I clearly defined the parameters for different types: one is a class, and the other is a structure.

Why is this not enough to make different signatures?

+5
generics c # method-overloading
source share
4 answers

Jon Skeet has an answer for everything: click me

quote:

ads differ only in general restrictions, and restrictions are not part of the signature

+3
source share

You can do this, you need to create something like enable_if from C ++

 public class ClassTag<V> where V : class { } public class StructTag<V> where V : struct { } public void Func<V>(V v, ClassTag<V> dummy = null) where V : class { Console.Writeln("class"); } public void Func<V>(V v, StructTag<V> dummy = null) where V : struct { Console.Writeln("struct"); } public void Func<V>(V? v, StructTag<V> dummy = null) where V : struct { Console.Writeln("struct?"); } static void Main() { Func("A"); Func(5); Func((int?)5); } 

It can be extended to use any non-overlapping where to distinguish between overloads. The only drawback is that it cannot be used inside another general method:

 public static void Z1<T>(T t) // where T : class { Func(t); //error there } public static void Z2<T>(T t) where T : class { Func(t); //ok } 

edit But it is possible to use dynamic in this case to circumvent this limitation:

 public static void Z1<T>(T t) { Func((dynamic)t); //if `T == int` it will call "struct" version } 

The only drawback is the runtime cost, similar to calling the Dictionary<,> index.

+5
source share

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.

+1
source share

If you do not need general parameters and just need to distinguish between these cases at compile time, you can use the following code.

 void Foo(object a) { } // reference type void Foo<T>(T? a) where T : struct { } // nullable void Foo(ValueType a) { } // value type 
+1
source share

All Articles