-1

I'm new to C# and I'm trying to make a generic type dispatcher (C++ like), but it looks like I can't call a function with a generic parameter:

Sample code:

class Program
{
    static void Main(string[] args)
    {
        DoSomething(1);
    }

    static void DoSomething<T>(T value)
    {
        //error CS1503: Argument 1: cannot convert from 'T' to 'int'
        DoSomethingElse(value);
    }

    static void DoSomethingElse(int a)
    {
        Console.Write("int\n");
    }

    static void DoSomethingElse(float a)
    {
        Console.Write("float\n");
    }
}

Can you please explain why I can't call DoSomethingElse from DoSomething? How may I forward value to another function that accepts that specific type?

Mircea Ispas
  • 20,260
  • 32
  • 123
  • 211
  • 1
    because do something else is a different type... – johnny 5 Apr 04 '17 at 15:51
  • @johnny5 `DoSomethingElse` is a function that can be called with any `int` and `value` is of type `int`. I don't understand your comment – Mircea Ispas Apr 04 '17 at 15:52
  • Possible duplicate of [C# generic interface specialization](http://stackoverflow.com/questions/2229084/c-sharp-generic-interface-specialization) – Owen Pauling Apr 04 '17 at 15:53
  • 4
    @Felics No, `value` is of type `T`, not `int`. The compiler has of course already told you as much. – Servy Apr 04 '17 at 15:53
  • @Felics, at compile time it doesn't know it can do that, you can check explicitly for the type int and then cast but that seems a bit wierd – johnny 5 Apr 04 '17 at 15:53
  • 3
    Basically, generics work very different to templates. In .NET, the IL **knows about T** - it isn't boiled away by the compiler. – Marc Gravell Apr 04 '17 at 15:55
  • @Servy isn't `T` an alias for `int`? In C++ `std::is_same_v` is true... – Mircea Ispas Apr 04 '17 at 15:55
  • 3
    Why is this necessary? If DoSomethingElse() is called directly, the correct polymorph will be found at compile time. To do this at runtime, I think you'll have to add some logic. This feels like an XY problem, though. What is it you're really trying to do here? – Marc L. Apr 04 '17 at 15:56
  • 1
    @Felics To the compiler it's it's own type, distinct from every other type. – Servy Apr 04 '17 at 15:57
  • 2
    @Felics no, `T` is `T`. The `T` can in theory be anything, and the IL preserves that. You could have `DoSomething(1)` and `DoSomething("abc")`, but that would still be **one** (single) IL method, not 2 – Marc Gravell Apr 04 '17 at 15:57
  • @MarcL. this is just a simple example to reduce the real problem to something suite for a SO question. The function will dispatch the value to different functions based on a second parameter. – Mircea Ispas Apr 04 '17 at 15:57
  • @MarcGravell so basically it will be one function, not 2 (or more) as in C++... – Mircea Ispas Apr 04 '17 at 15:58
  • 3
    The key thing about generics is that you are conceptually doing **the same** thing with each T. Choosing an overload based on the type is the **opposite** of "generic" – Marc Gravell Apr 04 '17 at 15:58
  • @Felics yes, one function; you can see that using ILDasm or similar – Marc Gravell Apr 04 '17 at 15:59

2 Answers2

3

This is probably not the cleanest way to deal with it, but you can cast to dynamic. The problem is, if you don't have a method implemented for whatever type T is, it will crash at runtime. You may be able to narrow that problem a bit by adding a constraint to your generic parameter.

static void DoSomething<T>(T value) where T : struct
{
    DoSomethingElse((dynamic)value);
}
MrZander
  • 3,031
  • 1
  • 26
  • 50
  • 4
    Another problem here is that this introduces boxing and the DLR - but yes, it will work – Marc Gravell Apr 04 '17 at 16:00
  • 2
    This is what I wanted. Unfortunately this will give a runtime error instead of a compile time error in the case DoSomethingElse is missing... – Mircea Ispas Apr 04 '17 at 16:01
  • @MarcGravell Can you explain what boxing is? edit, nvm, found a good resource. Thanks for the input! – MrZander Apr 04 '17 at 16:08
2

Without any constraints, you are only permitted to do activities that you could do if value is of type object. You can use the is and as keywords to work around this.

If you add a where constraint such as where T : IComparable, you are permitted to use methods and properties that meet the constraint.

i.e. with the IComparable constraint, you could call value.CompareTo(x)

static void DoSomething<T>(T value)
{
    if (value is int)
    {
        DoSomethingElse((value as int?).Value);
    }
    else if (value is float)
    {
        DoSomethingElse((value as float?).Value);
    }
    else
    {
        throw new Exception("No handler for type " + typeof(T).Name);
    }
}
Grax32
  • 3,986
  • 1
  • 17
  • 32