8

The program below produces this output:

Foo<T> called

Process is terminated due to StackOverflowException.

So, Foo(baz) calls the generic Foo<T>, but Bar(baz) recurses and does not call Bar<T>.

I'm on C# 5.0 and Microsoft .NET. The compiler seems to choose the generic method, instead of recursion, when the non-generic method is an override.

Where can I find an explanation for this rule? (I had guessed that the compiler would choose recursion in both cases.)

Here is the program in its entirety:

using System;

namespace ConsoleApplication1 {
    class Baz { }

    abstract class Parent {
        public abstract void Foo(Baz baz);
    }

    class Child : Parent {
        void Bar<T>(T baz) {
            Console.WriteLine("Bar<T> called");
        }

        public void Bar(Baz baz) {
            Bar(baz);
        }

        void Foo<T>(T baz) {
            Console.WriteLine("Foo<T> called");
        }

        public override void Foo(Baz baz) {
            Foo(baz);
        }
    }

    class Program {
        static void Main(string[] args) {
            var child = new Child();
            child.Foo(null);
            child.Bar(null);
            Console.ReadLine();
        }
    }
}
qxn
  • 17,162
  • 3
  • 49
  • 72
  • I like to view all such situations as this as the compiler creators' way of punishing you for using inheritance... – David Arno Jun 30 '15 at 22:06
  • Seems as though priority is being given to the non-overridden method in the child class over the overridden method in the child class; it may treat the overridden method as being part of the parent. – adamdc78 Jun 30 '15 at 22:07

3 Answers3

5

According to the MSDN docs, priority is given to method signatures that are not overridden. Since the non-generic version of Foo is overridden, it immediately goes to the bottom of the priority of choosing a method. In general terms, the next step is to choose the most specific method possible and execute it. In the case of the Bar methods, the Bar(Baz baz) method will always be the most specific in your case.

Overload resolution is a compile-time mechanism for selecting the best function member to invoke given an argument list and a set of candidate function members. Overload resolution selects the function member to invoke in the following distinct contexts within C#:

  • Invocation of a method named in an invocation-expression (Section 7.5.5). Invocation of an instance constructor named in an object-creation-expression (Section 7.5.10.1).
  • Invocation of an indexer accessor through an element-access (Section 7.5.6). Invocation of a predefined or user-defined operator referenced in an expression (Section 7.2.3 and Section 7.2.4).

Each of these contexts defines the set of candidate function members and the list of arguments in its own unique way, as described in detail in the sections listed above. For example, the set of candidates for a method invocation does not include methods marked override (Section 7.3), and methods in a base class are not candidates if any method in a derived class is applicable (Section 7.5.5.1).

MSDN Overload Resolution

I bolded the text that I think relates to your question.

Here's another question on Stack Overflow that might help out. It talks about method resolution in general. Doesn't touch on overridden methods, but helps fill in some of the process that I didn't touch on.

Community
  • 1
  • 1
Nathan
  • 1,437
  • 12
  • 15
1

Overload resolution searches up inheritance chains, looking for the methods defined at each point.

Child defines void Foo<T>(T baz) but does not define void Foo(Baz baz) so void Foo<T>(T baz) is chosen.

Generally this makes sense; in real code if Foo<T>(T baz) did not do a very similar job to what Foo(Baz baz) in the base did when passed a Baz, then your design is confusing and you should pick a new name.

You can kludge things a bit with using public new void Foo(Baz baz) or public new virtual void Foo(Baz baz) to force an override to be defined in Child too (though here there'd need to be an intermediate step in the hierarchy so that the abstract method has an implementation) which could call base.Foo(baz) (to call into a base implementation) and/or Foo<Baz>(baz) (to call into the generic version)`, but such tricks are best avoided.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
0

maybe it behave like when you implement something like this

    void myMethod(long? l) { }
    void myMethod(int? i) { }

calling it with null will use the int?

adding this one

    void myMethod(short? i) { }

and still calling it with null, the code will switch to short?

maybe there is an internal order/priority being done?

now with your code, i'm giving this just to show the difference between letting the compiler decide and the programmer decide(explicit call)

this is the generic Baz

.method private hidebysig 
    instance void Bar<T> (
        !!T baz
    ) cil managed 
{
    // Method begins at RVA 0x2060
    // Code size 13 (0xd)
    .maxstack 8

    IL_0000: nop
    IL_0001: ldstr "Bar<T> called"
    IL_0006: call void [mscorlib]System.Console::WriteLine(string)
    IL_000b: nop
    IL_000c: ret
} // end of method Child::Bar

your implementation

    public void Bar(Baz baz) {
        Bar(baz);
    }

give this

.method public hidebysig 
    instance void Bar (
        class ConsoleApplication1.Baz baz
    ) cil managed 
{
    // Method begins at RVA 0x206e
    // Code size 10 (0xa)
    .maxstack 8

    IL_0000: nop
    IL_0001: ldarg.0
    IL_0002: ldarg.1
    IL_0003: call instance void ConsoleApplication1.Child::Bar(class ConsoleApplication1.Baz)
    IL_0008: nop
    IL_0009: ret
} // end of method Child::Bar

this one

    public void Bar(Baz baz)
    {
        Bar<Baz>(baz);
    }

give this

.method public hidebysig 
    instance void Bar (
        class ConsoleApplication1.Baz baz
    ) cil managed 
{
    // Method begins at RVA 0x206e
    // Code size 10 (0xa)
    .maxstack 8

    IL_0000: nop
    IL_0001: ldarg.0
    IL_0002: ldarg.1
    IL_0003: call instance void ConsoleApplication1.Child::Bar<class ConsoleApplication1.Baz>(!!0)
    IL_0008: nop
    IL_0009: ret
} // end of method Child::Bar
Fredou
  • 19,848
  • 10
  • 58
  • 113