views:

107

answers:

5

I have a generic class which takes two type parameters, Generic<A, B>. This class has methods with signatures that are distinct so long and A and B are distinct. However, if A == B the signatures match exactly and overload resolution cannot be performed. Is it possible to somehow specify a specialisation of the method for this case? Or force the compiler to arbitrarily choose one of the matching overloads?

using System;

namespace Test
{
    class Generic<A, B>
    {
        public string Method(A a, B b)
        {
            return a.ToString() + b.ToString();
        }

        public string Method(B b, A a)
        {
            return b.ToString() + a.ToString();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Generic<int, double> t1 = new Generic<int, double>();
            Console.WriteLine(t1.Method(1.23, 1));

            Generic<int, int> t2 = new Generic<int, int>();
// Following line gives:
//     The call is ambiguous between the following methods
//     or properties: 'Test.Generic<A,B>.Method(A, B)' and
//     'Test.Generic<A,B>.Method(B, A)'
            Console.WriteLine(t2.Method(1, 2));   
        }
    }
}
A: 

No.

If you want the compiler to decide things arbitrarily, what is the purpose of you calling the method?

shahkalpesh
One of the matching overloads, not some random method. Both matching overloads must have equivalent behaviour or the class would be broken anyway.
Dave
+2  A: 

Given the purely generic definition there is no way to force the compiler to choose an overload. It has no way to distinguish a winner between the two methods.

It may seem a good idea to just pick one or the other but the decision needs to be deterministic. Even something as simple as the first one in the file is not really doable because you must consider partial classes. How would the compiler choose the first method if each were in a different file?

What you can do though is add a non-generic version of the method which accepts int. The compiler will choose the non-generic version over the generic version and it will produce a win in this very limited scenario. You would have to repeat that for every type which may have a conflict though.

For example. Adding this method will solve your compilation error, but only for int.

public string Method(int b, int a)
{
    return b.ToString() + a.ToString();
}
JaredPar
+1  A: 

I know it kind of defeats the purpose of the generic, but what about defining the method once, taking two parameters of type object?

Inside the method, you can examine the types and work out which one of your two options to call.

namespace Test
{
    class Generic<A, B>
    {
        public string Method(object a, object b)
        {
            if (a is A && b is B)
                return MethodOneTwo;
            else if (a is B && b is A)
                return MethodTwoOne;
            else
                throw new ArgumentException("Invalid Types");
        }

        private string MethodOneTwo(A a, B b)
        {
            return a.ToString() + b.ToString();
        }

        private string MethodTwoOne(B b, A a)
        {
            return b.ToString() + a.ToString();
        }
    }
}
Damovisa
A: 

This will use reflection to get the Methods and arbitrarily call one. You could make this more robust by filtering on the parameter and return types that are expected whenever you are getting the methods.

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1
{
    using System;
    using System.Reflection;

    namespace Test
    {
        class Generic<A, B>
        {
            public string Method(A a, B b)
            {
                return a.ToString() + b.ToString();
            }

            public string Method(B b, A a)
            {
                return b.ToString() + a.ToString();
            }
        }

        class Program
        {
            static void Main(string[] args)
            {
                Generic<int, double> t1 = new Generic<int, double>();
                Console.WriteLine(t1.Method(1.23, 1));

                Generic<int, int> t2 = new Generic<int, int>();
                // Following line gives:
                //     The call is ambiguous between the following methods
                //     or properties: 'Test.Generic<A,B>.Method(A, B)' and
                //     'Test.Generic<A,B>.Method(B, A)'
               MethodInfo [] methods = t2.GetType().GetMethods();
                foreach(MethodInfo method in methods)
                {
                    if (method.Name == "Method")
                    {
                        method.Invoke(t2,new Object[2] {1,2});
                        break;
                    }
                }
            }
        }
    }
}

Edit: Here is a blog about the problem you face, with a solution similar to Jared's.

http://shiman.wordpress.com/2008/07/07/generic-method-overload-a-trap-for-c-net-library-developers/

What we really need are templates that generate concrete signatures at precompile or compile time.

AaronLS
+1  A: 

Thanks for the good answers, they prompted me into this solution:

using System;

namespace Test
{
    class Generic<A, B>
    {
        public string Method(A a, B b)
        {
            return this.DefaultMethod(a, b);
        }

        protected string DefaultMethod(A a, B b)
        {
            return a.ToString() + b.ToString();
        }

        public string Method(B b, A a)
        {
            return b.ToString() + a.ToString();
        }
    }

    class Generic<A> : Generic<A, A>
    {
        public new string Method(A a, A b)
        {
            return base.DefaultMethod(a, b);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Generic<int, double> t1 = new Generic<int, double>();
            Console.WriteLine(t1.Method(1.23, 1));

            Generic<int> t2 = new Generic<int>();
            Console.WriteLine(t2.Method(1, 2));
        }
    }
}
Dave
Nice solution. Didn't think of just creating two generic classes :)
Damovisa