Почему явные вызовы интерфейса на 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 Удовлетворением ограничений в спецификации С#.
Генераторы С# не ведут себя как шаблоны С++ в этом отношении.