5

I want to have two overloads of a generic method:

public ReturnType<T> DoStuff<T>(T thing) {...}

public ReturnType<T> DoStuff<T>(IEnumerable<T> things) {...}

Trouble is, of course, that an IEnumerable<T> is itself a type that matches the first signature, so when I try passing a collection to this method, it invokes the first overload.

Obviously I could name the methods differently to remove the ambiguity. But seeing as the methods essentially do the same thing, I'd like to keep them as overloads.

Is there some way of defining T in the first signature so that it will not accept an IEnumerable as an argument?

Shaul Behr
  • 36,951
  • 69
  • 249
  • 387
  • I'm not aware of the possibility of giving an _exclusive_ command for an overload type "best" choice. You may want to add a dummy parameter to the 2nd method and differentiate the signature – user3598756 Oct 09 '16 at 11:32
  • well, the generic type - by definition - is more precise than an `IEnumerable` when you don't pass in that exact type. You could change it from the caller's point of view by doing an `.AsEnumerable()` call before that – Jeroen Vannevel Oct 09 '16 at 11:32

2 Answers2

6

It's a hack, but you can misuse the fact that extension methods are only considered when no match has been found in non-extension methods.

class MyClass {
  public ReturnType<T> DoStuff<T>(IEnumerable<T> things) { ... }
  public ReturnType<T> DoStuffSingle<T>(T thing) { ... }
}

static class MyClassExtensions {
  public static ReturnType<T> DoStuff<T>(this MyClass myClass, T thing)
    => myClass.DoStuffSingle(thing);
}

After this, given a MyClass myClass;:

  • myClass.DoStuff(123); calls the extension method taking int
  • myClass(new[] {123}); calls the instance method taking IEnumerable<int>
  • myClass("123"); calls the instance method taking IEnumerable<char>
  • myClass(t);, where t is of an unconstrained generic parameter type T, calls the extension method taking T, regardless of which interfaces T implements.

Those last two are an indication that you probably shouldn't continue down this path, in my opinion, but there's nothing stopping you from disagreeing and going with this anyway.

0

I don't think there is a way to say that you want to except "everything except X" but you can use generic constraints to specify what the T should be like:

public ReturnType<T> DoStuff<T>(T thing) where T : IYourInterface {...}    
public ReturnType<T> DoStuff<T>(IEnumerable<T> things) where T : IYourInterface {...}

I think it makes sense that there isn't away (without adding more code) to do so because generics is compiled to actual classes. If you specify "everything except X" then what should it compile to? What functions will it have?..

To better understand why to use T and not to just pass a IYourInterface parameter see Difference between interface as type constraint and interface as parameter?. Also, this might provide a bit more explanation

Community
  • 1
  • 1
Gilad Green
  • 36,708
  • 7
  • 61
  • 95
  • In that case, why bother with `T`? Just replace `T` in the signatures with `IYourInterface`. The point being that this is a generic, meaning it must be usable for any type, including ones I haven't thought of. – Shaul Behr Oct 09 '16 at 11:32
  • 2
    @ShaulBehr If you call it with an `IEnumerable`, you may want a return type of `ReturnType` rather than `ReturnType`. You may be right that this can't work for you, but it's a valid approach in other cases. –  Oct 09 '16 at 11:35
  • @hvd fair comment. But my point still stands that this also needs to work for classes that don't implement `IYourInterface`. – Shaul Behr Oct 09 '16 at 11:36
  • @ShaulBehr See: [Difference between interface as type constraint and interface as parameter?](http://stackoverflow.com/questions/7889799/difference-between-interface-as-type-constraint-and-interface-as-parameter) – Gilad Green Oct 09 '16 at 11:37