Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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".

like image 885
Fanblade Avatar asked Jul 22 '16 21:07

Fanblade


2 Answers

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.

like image 192
Kyle Avatar answered Nov 15 '22 08:11

Kyle


Adding to Kyle's answer, which I cannot do in a comment because I do not have sufficient reputation yet...

I think this is telling:

private static string GenericMethod<T>(T foo) where T : class, IBase
{
    return foo.Method() + " "  + typeof(T) + " " + typeof(Foo);
}

Removing object and having the parameter be a T, so the as-cast is unnecessary still calls the IBase.Method.

I’m pretty sure this is all directly due to 4.4.4 Satisfying Constraints in the C# specification.

C# generics do not behave like C++ templates in this regard.

like image 37
Eljay Avatar answered Nov 15 '22 09:11

Eljay