Запечатанное ключевое слово влияет на мнение компилятора на литье
У меня есть ситуация, когда мне хотелось бы объяснить поведение компилятора. Учитывая небольшой код:
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
.
Ответы
Ответ 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
}