Полиморфизм, не работающий для вызова из общего класса в С#

Похоже, что в следующем случае полиморфизм не работает должным образом У меня следующие определения:

interface BaseInterface{}
interface NewInterface:BaseInterface{}
class NewClass:NewInterface{}

class GenericClass<T> where T:BaseInterface
{
    public string WhoIAm(T anObject)
    {
        return TestPolymorphism.CheckInterface(anObject);
    }
}

class ImplementedClass:GenericClass<NewInterface>{}

class TestPolymorphism
{
    public static string CheckInterface(BaseInterface anInterface)
    {
        return "BaseInterface";
    }

    public static string CheckInterface(NewInterface anInterface)
    {
        return "NewInterface";
    }
}

Тогда, когда я вызываю:

NewClass nc = new NewClass();
ImplementedClass impClass = new ImplementedClass();
Console.WriteLine("The result is " + impClass.WhoIAm(nc));

У меня есть "Результат BaseInterface"

Я ожидал, что "Результатом будет NewInterface", поскольку nc реализует BaseClass и NewClass
Каким будет лучший способ получить "NewClass" в результате?

Спасибо

Ответ 1

Помните, что при использовании общих методов эти вызовы не виртуального метода все еще разрешаются во время компиляции самого генерика, не во время компиляции реализации родового.

Таким образом:

class GenericClass<T> where T:BaseInterface
{
    public string WhoIAm(T anObject)
    {
        return TestPolymorphism.CheckInterface(anObject);
    }
}

Будет разрешено перегрузка, которая принимает BaseInterface, потому что на самом деле это то, что вы ограничили, независимо от типа T.

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

Таким образом, ваш GenericClass<T> компилируется до того, как будет рассмотрено его использование (что сильно отличается от того, как С++ делает шаблоны - оба метода имеют свои плюсы и минусы). Итак, если у вас есть неограниченный общий (скажем, просто T), то он считается object для целей перегрузки (грубо говоря), но если у вас есть ограниченный общий (скажем where T : BaseInterface), то он считается BaseInterface для целей перегрузки.

В этом случае вы увидите нечто подобное:

public static bool UberEquals<T>(T left, T right) where T : class
{
    return left == right;
}

Итак, вы думаете, если вы вызвали это:

var s1 = "ello";
var s2 = "Hello";

UberEquals<string>('H' + s1, s2);

Так как T является типом string, тогда он вызывает перегрузку string ==, но это не так, потому что вы не ограничены T, поэтому во время компиляции он принимает наименьший общий знаменатель object и вместо этого использует object ==.

Еще один способ подумать об этом:

BaseInterface bi = new ImplementedClass();

var x = TestPolymorphism.CheckInterface(bi);

X всегда будет говорить BaseInterface в приведенном выше, поскольку перегрузки разрешаются во время компиляции, а не динамически во время выполнения. Очень похоже на дженерики, просто имейте в виду, что общий компилируется до того, как он будет реализован, поэтому он может работать только с любым базовым классом или интерфейсом, который вы ограничиваете его с целью разрешения перегрузки.