3

I want to be able to something like this:

class A<T1, T2>
{
    public void Call(T1 arg1, T2 arg2)
    {
        B b = new B();
        b.DoSomething(arg1); // determine which overload to use based on T1
        b.DoSomething(arg2); // and T2
    }
}   

class B
{
    public void DoSomething(int x)
    {
        // ...
    }

    public void DoSomething(float x)
    {
       // ...
    }
}

I know it can be done with an if/else check, but that doesn't seem very elegant, especially when I have 20+ types to choose from.

Max
  • 12,622
  • 16
  • 73
  • 101
Tri-Edge AI
  • 330
  • 2
  • 15

4 Answers4

8

If you want type safety, the best you can do is probably:

class A<T1, T2>
{
    public void Call<T>(T1 arg1, T2 arg2) where T : IDoSomething<T1>, IDoSomething<T2>, new()
    {
        T b = new T();
        b.DoSomething(arg1);
        b.DoSomething(arg2);
    }
}

public interface IDoSomething<T>
{
    void DoSomething(T arg);
}

public class B : IDoSomething<int>, IDoSomething<float>
{
    void IDoSomething<int>.DoSomething(int arg)
    {
        Console.WriteLine("Got int {0}", arg);
    }

    void IDoSomething<float>.DoSomething(float arg)
    {
        Console.WriteLine("Got float {0}", arg);
    }
}

which you can use like:

var a = new A<int, float>();
a.Call<B>(1, 4.0f);
Lee
  • 142,018
  • 20
  • 234
  • 287
4

This approach is not type-safe or advisable.

You can declare a variable as dynamic, which means that its type will be resolved at runtime, instead of compile time.

public void Call(dynamic arg1, dynamic arg2)
{
    B b = new B();
    b.DoSomething(arg1); // determine which overload to use based on T1
    b.DoSomething(arg2); // and T2
} 

Beware that if arg1 turns out to be, lets say, a string and no proper overload exists, you'll get a RuntimeBinderException. Also, there's a slight performance hit, because you're deferring type resolution.

dcastro
  • 66,540
  • 21
  • 145
  • 155
  • I cannot use dynamic in the environment I am working with, so that's not an option. – Tri-Edge AI Jan 28 '14 at 18:29
  • I think that this is a silly use of `dynamic` and does not necessarily answer the question. The question is "Can C# generics be this cool?" and that precludes use of `dynamic`. – Michael J. Gray Jan 28 '14 at 18:46
  • @MichaelJ.Gray Actually, many posts on stack overflow ([1](http://stackoverflow.com/questions/7210017/method-overload-resolution-using-dynamic-argument), [2](http://stackoverflow.com/questions/5835989/method-overloading-and-dynamic-keyword-in-c-sharp)) show this usage of the `dynamic` keyword. I'm not saying you *should* use it (I wouldn't), I'm showing that you can use it to dynamically determine which method overload should be invoked (which seems to be the OP's problem and the core of his question). – dcastro Jan 28 '14 at 18:53
  • @dcastro As far as others on SO using it, that doesn't mean it's proper in this case. Plenty of answers on SO suggest terrible ideas that break so many good practices it's not even funny. A sad fact about the industry: many programmers are really bad at what they do. I'm not saying you are, but I want to make it clear that just because others are doing it does not mean you need to. With that said, I accept your answer as an answer, which is what it is. Nothing more, nothing less. I was just putting my $0.02 explaining that I think it's silly to use `dynamic` over what Lee is suggesting. – Michael J. Gray Jan 28 '14 at 19:01
  • @MichaelJ.Gray I suppose you're right. While my purpose was to simply illustrate one possible use of the keyword, I guess that to some readers it might come across as an encouraged solution. I'll edit my post to make the point more obvious. – dcastro Jan 28 '14 at 19:08
  • @dcastro It is definitely an alternative, don't get me wrong. In my opinion, it's just not the best solution. I do notice now that your "can" is in italics, making me believe that you are implying it should be read as "you can, but it's a bad idea [...]". This makes me feel better about the post and your clarification makes it very obvious. +1 – Michael J. Gray Jan 28 '14 at 19:09
0

Yes, they are that cool.

However, it will only work with inheritance hierarchies because in your example: arg1 must be an int and arg2 must be a float. You will receive a compiler error because your type declarations T1, T2 are not limited to types float and int.

If arg1 were BaseA and arg2 were BaseB then the example would make more since.

Then you would declare the class A like so:

class A<T1, T2> where T1: BaseA, T2: BaseB

Now for any case where T1 inherits from BaseA, etc. it will work.

When you use the class A the runtime will know what type is actually being used and choose the correct overload.

Rick Love
  • 12,519
  • 4
  • 28
  • 27
  • Note, this doesn't work with primitive data types: [C# generic method with integer constraint refuses to cast from integer to the generic type](http://stackoverflow.com/questions/11770882/c-sharp-generic-method-with-integer-constraint-refuses-to-cast-from-integer-to-t). The OP might actually be using class types, but the examples in the question use `int` and `float` – valverij Jan 28 '14 at 18:33
0

You can do this but only under specific cases if you can use where constraints on the generic and the compiler knows what to expect.

However, it HAS to know what to expect unless you're using dynamic. Note also that the where generic type constraint has to be a class or interface. It can't be (for example) an int.

Simple Example that will compile and run just fine:

static void Main(string[] args)
{
    A<C, D> test = new A<C, D>();
    test.Call(new Cimpl(), new Dimpl());
}

class A<T1, T2> where T1 : C where T2 : D
{
    public void Call(T1 arg1, T2 arg2)
    {
        B b = new B();
        b.DoSomething(arg1); // determine which overload to use based on T1
        b.DoSomething(arg2); // and T2
    }
}

class Cimpl : C { }

class Dimpl : D { }

interface C
{ }

interface D
{ }

class B
{
    public void DoSomething(C x)
    {
        // ...
    }

    public void DoSomething(D x)
    {
        // ...
    }
}
McAden
  • 13,714
  • 5
  • 37
  • 63