Подтвердить что ты не робот

Запечатанное ключевое слово влияет на мнение компилятора на литье

У меня есть ситуация, когда мне хотелось бы объяснить поведение компилятора. Учитывая небольшой код:

interface IFoo<T>
{
    T Get();
}

class FooGetter : IFoo<int>
{
    public int Get()
    {
        return 42;
    }
}

Следующие компилируются и запускаются:

static class FooGetterGetter
{
    public static IFoo<T> Get<T>()
    {
        return (IFoo<T>)new FooGetter();
    }
}

Если мы сделаем изменение в сигнатуре класса Foo и добавим ключевое слово sealed:

sealed class FooGetter : IFoo<int> // etc

Затем я получаю ошибку компилятора в следующей строке:

 return (IFoo<T>)new FooGetter();

Из:

Невозможно преобразовать тип 'MyNamespace.FooGetter' в 'MyNamespace.IFoo <T> '

Может кто-нибудь объяснить, что здесь происходит в отношении ключевого слова sealed? Это С# 4 в отношении .NET.NET-проекта в Visual Studio 2010.

Обновление: интересно, я наткнулся на эту часть поведения, когда мне было интересно, почему следующий код исправляет его, когда применяется sealed:

return (IFoo<T>)(IFoo<int>)new FooGetter();

Обновление: только для уточнения, все работает нормально, когда тип запрошенной T совпадает с типом T, используемым конкретным типом. Если типы отличаются, то при выполнении команды при выполнении чего-то вроде:

Невозможно передать объект типа "MyNamespace.StringFoo" для ввода 'MyNamespace.IFoo`1 [System.Int32]'

В приведенном выше примере StringFoo : IFoo<string>, и вызывающий абонент попросит получить int.

4b9b3361

Ответ 1

Потому что FooGetter является явной реализацией IFoo<int> вместо реализации IFoo<T> в общем случае. Поскольку он запечатан, компилятор не знает, как отличить его от общего IFoo<T>, если T есть что-то иное, чем int. Если он не был запечатан, компилятор разрешил бы ему компилировать и выдавать исключение во время выполнения, если T не был int.

Если вы попытаетесь использовать его с чем-либо, кроме int (например, FooGetterGetter.Get<double>();), вы получите исключение:

Невозможно передать объект типа "MyNamespace.FooGetter" для ввода "MyNamespace.IFoo`1 [System.Double]".

Я не уверен, почему компилятор не создает ошибку для непечатаемой версии. Как ваш подкласс FooGetter такой, что new FooGetter() даст вам что-нибудь, что реализует IFoo<{something_other_than_int}>?

Update:

Per Дэн Брайант и Andras Zoltan существуют методы возврата производного класса из конструктора (или возможно, более точно, чтобы компилятор возвращал другой тип, анализируя атрибуты). Так что технически это возможно, если класс не запечатан.

Ответ 2

Если класс в незапечатанном производном классе может реализовать IFoo<T>:

class MyClass : FooGetter, IFoo<double> { }

Когда FooGetter помечается как запечатанный, компилятор знает, что это не может быть возможно для каких-либо дополнительных реализаций IFoo<T>, кроме IFoo<int> может существовать FooGetter.

Это хорошее поведение, это позволяет вам ловить проблемы с вашим кодом во время компиляции, а не во время выполнения.

Причина, по которой работает (IFoo<T>)(IFoo<int>)new FooGetter();, заключается в том, что теперь вы представляете свой закрытый класс как IFoo<int>, который может быть реализован во что угодно. Это также хорошая работа, поскольку вы не случайно, но целенаправленно переопределяете проверку компилятора.

Ответ 3

Просто добавьте к существующим ответам: это действительно не имеет ничего общего с используемыми генериками.

Рассмотрим этот более простой пример:

interface ISomething
{
}

class OtherThing
{
}

Затем, говоря (внутри метода):

OtherThing ot = XXX;
ISomething st = (ISomething)ot;

работает просто отлично. Компилятор не знает, может ли OtherThing быть ISomething, поэтому он верит нам, когда мы скажем, что это будет успешным. Однако, если мы изменим OtherThing на закрытый тип (а именно sealed class OtherThing { } или struct OtherThing { }), то литье больше не разрешено. Компилятор знает, что он не может идти хорошо (за исключением того, что ot должен быть null, но правила С# по-прежнему запрещают отливку с закрытого типа на интерфейс, не реализованный этим закрытым типом).

Относительно обновления вопроса: Написание (IFoo<T>)(IFoo<int>)new FooGetter() не сильно отличается от записи (IFoo<T>)(object)new FooGetter(). Вы можете "разрешить" любой актерский состав (с дженериками или без), пройдя через некоторый промежуточный тип, который, безусловно,/возможно, является предком для обоих типов, которые вы хотите преобразовать. Он очень похож на этот шаблон:

void MyMethod<T>(T t)    // no "where" constraints on T
{
  if (typeof(T) = typeof(GreatType))
  {
    var tConverted = (GreatType)(object)t;
    // ... use tConverted here
  }
  // ... other stuff
}