68

I would like to differentiate between following cases:

  1. A plain value type (e.g. int)
  2. A nullable value type (e.g. int?)
  3. 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)?

Pierre Arnaud
  • 10,212
  • 11
  • 77
  • 108

8 Answers8

80

Constraints are not part of the signature, but parameters are. And constraints in parameters are enforced during overload resolution.

So let's put the constraint in a parameter. It's ugly, but it works.

class RequireStruct<T> where T : struct { }
class RequireClass<T> where T : class { }

static void Foo<T>(T a, RequireStruct<T> ignore = null) where T : struct { } // 1
static void Foo<T>(T? a) where T : struct { } // 2
static void Foo<T>(T a, RequireClass<T> ignore = null) where T : class { } // 3

(better six years late than never?)

Alcaro
  • 1,664
  • 16
  • 21
  • 2
    Ha, great idea! In fact, you do not need to add the `ignore` parameter to the second `Foo` function taking `T?`. – Pierre Arnaud Apr 24 '16 at 06:57
  • 1
    This gave me the opportunity to blog about the topic on http://code.fitness/post/2016/04/generic-type-resolution.html – Pierre Arnaud Apr 24 '16 at 08:22
  • 1
    I got the idea from [one of Eric Lippert's blog posts](https://blogs.msdn.microsoft.com/ericlippert/2009/12/10/constraints-are-not-part-of-the-signature/). I've always liked shenanigans. As for the T?, the situation in which I needed this only had cases 1 and 3, and I forgot testing if it's needed. – Alcaro Apr 28 '16 at 19:54
  • This is slick as snot. I like using "_" instead of "ignore" a la functional programming. – Seth Oct 03 '16 at 16:24
  • 1
    Simpler way, without the need for helper classes: Not sure if this is newer language version only or whatever. I guess it may cause an extra allocation of the struct, but I needed to test for equality to default anyway. `static void Foo(T? value) where T : struct { }` `static void Foo(T value, T defaultValue = default) where T : struct { }` `static void Foo(T obj) where T : class { }` – Jannes Jul 10 '20 at 23:55
22

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.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • 2
    +1. Of course, the first and second work because `T` and `T?` are different arguments. (`T` and `Nullable`) – Powerlord Jun 04 '10 at 13:28
  • 1
    +1 See: http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx – Anthony Pegram Jun 04 '10 at 13:31
  • Thank you for your quick reply; if I cannot differentiate the types, is there some way to get my last example to compile, by relaxing some constraint? – Pierre Arnaud Jun 04 '10 at 13:43
  • Ah, simply drop the `where T : struct` for method (1) and my example compiles. That's enough for me. – Pierre Arnaud Jun 04 '10 at 13:44
  • 2
    Actually, it is possible to differentiate the type of a method call based upon constraints if one doesn't mind having a dummy "optional" parameter of a generic reference type which has constraints on its generic arguments, and having a "null" default value for that parameter. Since the compiler will exclude from consideration any overload whose type cannot be constructed, the constraints in the type of the dummy parameter will be effective in limiting what overloads are considered. Of course, if a compiler can do that at a call site which doesn't give a value for a dummy argument, it... – supercat Apr 21 '16 at 18:57
  • 1
    ...should be just as capable of doing so without requiring the caller to pass a dummy argument, but I don't know any way to achieve that. – supercat Apr 21 '16 at 18:57
10

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 - either FooWithStruct<T> or FooWithClass<T>. For performance reasons we'll create and cache a strongly-typed delegate rather than using plain reflection every time the Foo<T> method is called.

int x = 42;
MyClass.Foo(x);    // displays "Non-Nullable Struct"

int? y = 123;
MyClass.Foo(y);    // displays "Nullable Struct"

string z = "Test";
MyClass.Foo(z);    // displays "Class"

// ...

public static class MyClass
{
    public static void Foo<T>(T? a) where T : struct
    {
        Console.WriteLine("Nullable Struct");
    }

    public static void Foo<T>(T a)
    {
        Type t = typeof(T);

        Delegate action;
        if (!FooDelegateCache.TryGetValue(t, out action))
        {
            MethodInfo mi = t.IsValueType ? FooWithStructInfo : FooWithClassInfo;
            action = Delegate.CreateDelegate(typeof(Action<T>), mi.MakeGenericMethod(t));
            FooDelegateCache.Add(t, action);
        }
        ((Action<T>)action)(a);
    }

    private static void FooWithStruct<T>(T a) where T : struct
    {
        Console.WriteLine("Non-Nullable Struct");
    }

    private static void FooWithClass<T>(T a) where T : class
    {
        Console.WriteLine("Class");
    }

    private static readonly MethodInfo FooWithStructInfo = typeof(MyClass).GetMethod("FooWithStruct", BindingFlags.NonPublic | BindingFlags.Static);
    private static readonly MethodInfo FooWithClassInfo = typeof(MyClass).GetMethod("FooWithClass", BindingFlags.NonPublic | BindingFlags.Static);
    private static readonly Dictionary<Type, Delegate> FooDelegateCache = new Dictionary<Type, Delegate>();
}

(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.)

Community
  • 1
  • 1
LukeH
  • 263,068
  • 57
  • 365
  • 409
  • 1
    Could one improve things by using an approach similar to `Comparer.Default`, e.g. create a private static generic class `FooInvoker` with a public field `FooMethod` of type `Action` (since `FooInvoker` would be inaccessible outside `MyClass` there would be no risk of outside code abusing the public field)? If class constructor for `FooInvoker` sets `FooMethod` appropriately, I think that would might avoid the need for a dictionary look-up at runtime (I don't know whether .net would need to perform one internally every time `Foo` was called). – supercat Feb 22 '12 at 18:09
  • 1
    See my posted answer for an outline of how one would use a static class. I probably made some syntax errors, since I'm typing from memory (and mostly program in vb.net), but there should be enough of an outline to get you going. – supercat Feb 26 '12 at 18:45
5

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.

      static void Foo( T? a ) where T : struct
      {
         // nullable stuff here
      }

      static void Foo( T a )
      {
         if( a is ValueType )
         {
            // ValueType stuff here
         }
         else
         {
            // class stuff
         }
      }
Marnix van Valen
  • 13,265
  • 4
  • 47
  • 74
  • 2
    @Maxim: Thank you. The problem I am facing is that in the non nullable method, I have to be able to invoke other functions which take and return `T?`, and this is not valid without the `where T : struct` constraint. – Pierre Arnaud Jun 04 '10 at 14:05
3

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):

static class FooInvoker<T>
{
  public Action<Foo> theAction = configureAction;
  void ActionForOneKindOfThing<TT>(TT param) where TT:thatKindOfThing,T
  {
    ...
  }
  void ActionForAnotherKindOfThing<TT>(TT param) where TT:thatOtherKindOfThing,T
  {
    ...
  }
  void configureAction(T param)
  {
    ... Determine which kind of thing T is, and set `theAction` to one of the
    ... above methods.  Then end with ...
    theAction(param);
  }
}

Note that Reflection will throw an exception if one attempts to create a delegate for ActionForOneKindOfThing<TT>(TT param) when TT does not comply with that method's constraints. Because the system validated the type of TT when the delegate was created, one can safely invoke theAction without further type-checking. Note also that if outside code does:

  FooInvoker<T>.theAction(param);

only the first call will require any Reflection. Subsequent calls will simply invoke the delegate directly.

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

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.

Overload resolution now has fewer ambiguous cases

Also see Selecting C# Version in your visual studio project

It will still see clashes with the following

Foo(x);
...
static void Foo<T>(T a) where T : class { } // 3
static void Foo<T>(T a) where T : struct { } // 3

But will correctly resolve

Foo(x);
...
static void Foo<T>(T a, bool b = false) where T : class { } // 3
static void Foo<T>(T a) where T : struct { } // 3
Sprotty
  • 5,676
  • 3
  • 33
  • 52
  • I tried C# 7.3 and it does not change the collision between methods (1) and (3) in my original question. I still get error _Type 'X' already defines a member called 'Foo' with the same parameter types_. – Pierre Arnaud Nov 15 '18 at 04:06
  • 1
    @PierreArnaud Seems I jumped the gun a bit. My case was slightly different, and as that worked I assumed it would your case. I've amended the reply to reflect this....Seems MS have improved this but their is still some work to do... – Sprotty Nov 15 '18 at 12:48
1

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

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

With the latest compilers the RequireX approach can be done without introducing extra types and using only nullable ones (see at the sharplab.io):

using System;
using static Foos;

int x = 1;
int? y = 2;
string z = "a";

Foo(x); // OK, calls (1)
Foo(y); // OK, calls (2)
Foo(z); // OK, calls (3)
class Foos
{
    public static void Foo<T>(T a, T? _ = null) where T : struct => Console.WriteLine(1); // 1
    public static void Foo<T>(T? a) where T : struct => Console.WriteLine(2); // 2
    public static void Foo<T>(T a, T? _ = null) where T : class => Console.WriteLine(3); // 3
}

Actually removing the 2nd parameter in the 3rd method also seems to work:

class Foos
{
    public static void Foo<T>(T a, T? _ = null) where T : struct => Console.WriteLine(1); // 1
    public static void Foo<T>(T? a) where T : struct => Console.WriteLine(2); // 2
    public static void Foo<T>(T a) where T : class => Console.WriteLine(3); // 3
}
Guru Stron
  • 102,774
  • 10
  • 95
  • 132