Почему явные вызовы интерфейса на generics всегда вызывают базовую реализацию?

Почему явные вызовы интерфейса С# в универсальном методе, который имеет ограничение типа интерфейса, всегда вызывают базовую реализацию?

Например, рассмотрим следующий код:

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();
    }
}

Этот код выводит следующее:

IDerived.Method
IBase.Method

Вместо того, что можно было бы ожидать:

IDerived.Method
IDerived.Method

Кажется, что нет пути (не до рефлексии) для вызова скрытой, более производной явной реализации интерфейса типа, определенного во время выполнения.

РЕДАКТИРОВАТЬ: Чтобы быть ясным, следующее, если проверка оценивается как true в вызове GenericMethod выше:

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

Итак, ответ не в том, что T всегда рассматривается как IBase из-за ограничения общего типа "где T: class, IBase".

Ответы

Ответ 1

Здесь следует помнить, что IBase.Method и IDerived.Method - это два совершенно разных метода. Мы просто дали им похожие имена и подписи. Так как все, что реализует IDerived, также реализует IBase, что означает, что он будет иметь два метода с именем Method без параметров. Один принадлежит IDerived, а один принадлежит IBase.

Весь компилятор знает, когда компиляция GenericMethod заключается в том, что общий параметр будет реализовывать не менее IBase, поэтому он может гарантировать только реализацию IBase.Method. Так что метод, который вызвал.

В отличие от шаблонов С++, общая подстановка не возникает, когда метод компилируется (который с шаблонами будет выполняться один раз для каждой комбинации используемых параметров шаблона). Вместо этого метод компилируется ровно один раз таким образом, что любой тип может быть заменен во время выполнения.

В вашем случае компилятор испускает IL для GenericMethod, который выглядит примерно так:

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         

Обратите внимание, что он явно вызывает IBase.Method. Нет никакого виртуального/переопределяющего отношения между этим методом и IDerived.Method, поэтому базой является все, что вызывается, независимо от того, какой тип заменяется на T во время выполнения.

Ответ 2

Добавление в ответ Кайла, который я не могу сделать в комментарии, потому что у меня пока нет достаточной репутации...

Я думаю, это говорит:

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

Удаление объекта и наличие параметра T, поэтому as-cast не нужен, но вызывает метод IBase.Method.

Я уверен, что все это напрямую связано с 4.4.4 Удовлетворением ограничений в спецификации С#.

Генераторы С# не ведут себя как шаблоны С++ в этом отношении.