6

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

I added to methods similar to these examples:

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

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

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

I'm surprised by this as I've clearly defined by parameters to be of different type: one is a class and the other a struct.

Why isn't this sufficient to make the signatures different?

Jon Egerton
  • 40,401
  • 11
  • 97
  • 129
  • 1
    Possible duplicate of [Generic constraints, where T : struct and where T : class](http://stackoverflow.com/questions/2974519/generic-constraints-where-t-struct-and-where-t-class). See also Eric Lippert's article [here](http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx). – Frédéric Hamidi Oct 14 '11 at 08:29
  • @Frederic: How did I miss that one!!! – Jon Egerton Oct 14 '11 at 08:30
  • Apparently the "Related" pane in the sidebar didn't pick it either, so it might be trickier than usual ;) – Frédéric Hamidi Oct 14 '11 at 08:32
  • Ah right - I did scan the list of suggestions. – Jon Egerton Oct 14 '11 at 08:42

4 Answers4

7

Is possible to do it, you need 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 expanded to use any disjoint where to distinguish between overloads. Only drawback is that It cant be used inside another generic 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 there is possibility of use dynamic in that case to work around this limitation:

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

Only drawback is run time cost similar to call to Dictionary<,> index.

Yankes
  • 1,958
  • 19
  • 20
4

Jon Skeet has an answer to everything: click me

quote:

the declarations only differ in generic constraints, and constraints aren't part of the signature

lsuarez
  • 4,952
  • 1
  • 29
  • 51
fixagon
  • 5,506
  • 22
  • 26
  • 1
    Updated link, for future readers (Jon posted the same article on his personal blog, here): http://codeblog.jonskeet.uk/2010/10/28/overloading-and-generic-constraints/ – Arin May 20 '15 at 19:15
1

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.

supercat
  • 77,689
  • 9
  • 166
  • 211
1

If you don't need generic parameters and just want to differentiate between these cases at compile time, you can use following code.

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