Search code examples
c#.netgenericsexplicit-interface

Why do explicit interface calls on generics always call the base implementation?


Why do explicit C# interface calls within a generic method that has an interface type constraint always call the base implementation?

For example, consider the following code:

public interface IBase
{
    string Method();
}

public interface IDerived : IBase
{
    new string Method();
}

public class Foo : IDerived
{
    string IBase.Method()
    {
        return "IBase.Method";
    }

    string IDerived.Method()
    {
        return "IDerived.Method";
    }
}

static class Program
{
    static void Main()
    {
        IDerived foo = new Foo();
        Console.WriteLine(foo.Method());
        Console.WriteLine(GenericMethod<IDerived>(foo));
    }

    private static string GenericMethod<T>(object foo) where T : class, IBase
    {
        return (foo as T).Method();
    }
}

This code outputs the following:

IDerived.Method
IBase.Method

Instead of what one might expect:

IDerived.Method
IDerived.Method

There seems to be no way (short of reflection) to call a hidden, more derived explicit interface implementation of a type decided at runtime.

EDIT: To be clear, the following if check evaluates to true in the GenericMethod call above:

if (typeof(T) == typeof(IDerived))

So the answer is not that T is always treated as IBase due to the generic type constraint "where T : class, IBase".


Solution

  • The key here is to remember that IBase.Method and IDerived.Method are two completely different methods. We just happened to give them similar names and signatures. Since anything that implements IDerived also implements IBase that means it will have two methods named Method taking no parameters. One belongs to IDerived and one belongs to IBase.

    All the compiler knows when compiling GenericMethod is that the generic parameter will implement at least IBase, so it can only guarantee that the IBase.Method implementation exists. So that's the method that's called.

    Unlike C++ templates, the generic substitution doesn't happen whenever the method is compiled (which with templates would happen once for every combination of template parameters that's used). Instead the method is compiled exactly once in such a way that any type can be substituted at runtime.

    In your case the compiler emits IL for GenericMethod that looks something like this:

    IL_0000:  ldarg.0     
    IL_0001:  isinst      <T> 
    IL_0006:  unbox.any   <T>
    IL_000B:  box         <T>    
    IL_0010:  callvirt    IBase.Method
    IL_0015:  ret         
    

    Notice it explicitly calls IBase.Method. There's no virtual/override relationship between that method and IDerived.Method so the base is all that's called, regardless of what type gets substituted for T in the runtime.