Запечатанное ключевое слово влияет на мнение компилятора о приведении - PullRequest
19 голосов
/ 18 января 2012

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

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 '

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

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

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

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

Невозможно привести объект типа 'MyNamespace.StringFoo' к типу. 'MyNamespace.IFoo`1 [System.Int32]'

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

Ответы [ 3 ]

8 голосов
/ 18 января 2012

Поскольку 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}>?

Обновление:

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

4 голосов
/ 18 января 2012

Когда класс в незапечатанном виде любой производный класс может реализовать IFoo<T>:

class MyClass : FooGetter, IFoo<double> { }

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

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

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

1 голос
/ 15 октября 2012

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

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

interface ISomething
{
}

class OtherThing
{
}

Затем произнесем (внутри метода):

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

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

По поводу обновления вопроса: Написание (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
}
...