views:

902

answers:

3

Say I have two overloaded versions of a C# method:

void Method( TypeA a ) { }
void Method( TypeB b ) { }

I call the method with:

Method( null );

Which overload of the method is called? What can I do to ensure that a particular overload is called?

+1  A: 

ambigous call. (compile time error).

Schildmeijer
hi,I tried following sample and did not get compile error; any thoughts why? class Program { void test1(ICollection<String> a) { Console.WriteLine("test1-ICollection"); } void test1(IList<String> a) { Console.WriteLine("test1-List"); } static void Main(string[] args) { Program p = new Program(); p.test1(null); Console.ReadKey(); } }
Krishna Kumar
but I got compile error when I changed one of the overloads to accept a parameter of type IComparer<String> class Program { void test1(IList<String> a) { Console.WriteLine("test1-List"); } void test1(IComparer<String> a) { Console.WriteLine("test1-IComparer"); } static void Main(string[] args) { Program p = new Program(); p.test1(null); Console.ReadKey(); } }
Krishna Kumar
+25  A: 

It depends on TypeA and TypeB.

  • If exactly one of them is applicable (e.g. there is no conversion from null to TypeB because it's a value type but TypeA is a reference type) then the call will be made to the applicable one.
  • Otherwise it depends on the relationship between TypeA and TypeB.
    • If there is an implicit conversion from TypeA to TypeB but no implicit conversion from TypeB to TypeA then the overload using TypeA will be used.
    • If there is an implicit conversion from TypeB to TypeA but no implicit conversion from TypeA to TypeB then the overload using TypeB will be used.
    • Otherwise, the call is ambiguous and will fail to compile.

See section 7.4.3.4 of the C# 3.0 spec for the detailed rules.

Here's an example of it not being ambiguous. Here TypeB derives from TypeA, which means there's an implicit conversion from TypeB to TypeA, but not vice versa. Thus the overload using TypeB is used:

using System;

class TypeA {}
class TypeB : TypeA {}

class Program
{
    static void Foo(TypeA x)
    {
        Console.WriteLine("Foo(TypeA)");
    }

    static void Foo(TypeB x)
    {
        Console.WriteLine("Foo(TypeB)");
    }

    static void Main()
    {
        Foo(null); // Prints Foo(TypeB)
    }
}

In general, even in the face of an otherwise-ambiguous call, to ensure that a particular overload is used, just cast:

Foo((TypeA) null);

or

Foo((TypeB) null);

Note that if this involves inheritance in the declaring classes (i.e. one class is overloading a method declared by its base class) you're into a whole other problem, and you need to cast the target of the method rather than the argument.

Jon Skeet
@Jon: Why is the behavior as you describe in the case an implicit conversion is available? I would rather have expected the opposite. Is it just a matter of definition?
0xA3
@divo: It's definitely a design decision, but probably the reason behind it is that TypeB method is pretty much a specialized case of TypeA method. I mean, for every valid arg, by default TypeA will be matched unless the object is known to be TypeB.This behavior for null ensures this consistency.
Mehrdad Afshari
Mehrdad is right - if you can convert from TypeB to TypeA, then TypeB is more specific than TypeA, so will be picked in preference.
Jon Skeet
Yeah, makes sense. Thanks for the explanation.
0xA3
+3  A: 

Jon Skeet has given a comprehensive answer, but from a design point of view you shouldn't depend on corner-cases of the compiler specification. If nothing else, if you have to look up what it does before you write it, the next person to try to read it won't know what it does either. It's not maintainable.

Overloads are there for convenience, and two different overloads with the same name should do the same thing. If the two methods do different things, rename one or both of them.

It's more usual for an overloaded method to have variants with varying numbers of parameters, and for the overload with less parameters to supply sensible defaults.

e.g. string ToString(string format, System.IFormatProvider provider) has the most parameters,
string ToString(System.IFormatProvider provider) supplies a default format, and
string ToString() supplies a default format and provider,

Anthony