密封关键字影响编译器';他对演员阵容的看法

本文关键字:看法 影响 关键字 编译器 密封 | 更新日期: 2023-09-27 18:20:35

我有一种情况需要解释编译器的行为。给定一个小代码:

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关键字发生了什么吗?这是针对Visual Studio 2010中的.NET 4项目的C#4。

更新:有趣的是,当我想知道为什么在应用sealed时以下代码会修复它时,我偶然发现了这部分行为:

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

更新:只是为了澄清,当请求的T类型与具体类型使用的T类型相同时,一切都会正常运行。如果类型不同,则强制转换在运行时会失败,出现以下情况:

无法将"MyNamespace.StringFoo"类型的对象强制转换为类型'MyNamespace.IFoo'1[System.Int32]'

在上面的示例中,StringFoo : IFoo<string>和呼叫者要求获得int

密封关键字影响编译器';他对演员阵容的看法

因为FooGetterIFoo<int>的显式实现,而不是一般实现IFoo<T>。由于它是密封的,编译器知道如果T不是int,就无法将它强制转换为通用IFoo<T>。如果它没有被密封,编译器将允许它进行编译,并在运行时抛出异常(如果T不是int)。

如果你试图将它与int(例如FooGetterGetter.Get<double>();)以外的任何东西一起使用,你会得到一个异常:

无法将"MyNamespace.FooGetter"类型的对象强制转换为"MyNamespace.IFoo`1[System.Double]"类型。

我不确定的是,为什么编译器不会为非密封版本生成错误。你的子类FooGetter怎么能让new FooGetter()给你实现IFoo<{something_other_than_int}>的任何东西?

更新:

根据Dan Bryant和Andras Zoltan的说法,有一些方法可以从构造函数返回派生类(或者更准确地说,对于,编译器可以通过分析属性返回不同的类型)。因此,从技术上讲,如果类没有密封,这是可行的。

当一个类处于未密封状态时,任何派生类都可以实现IFoo<T>:

class MyClass : FooGetter, IFoo<double> { }

FooGetter被标记为密封时,编译器知道除了IFoo<int>之外,IFoo<T>的任何其他实现都不可能存在于FooGetter

这是一种很好的行为,它允许您在编译时而不是在运行时发现代码的问题。

(IFoo<T>)(IFoo<int>)new FooGetter();之所以有效,是因为您现在将密封类表示为IFoo<int>,它可以由任何东西实现。这也是一个很好的解决方案,因为您并非偶然,而是有目的地覆盖编译器检查。

只是为了增加现有的答案:这实际上与所使用的泛型无关。

考虑这个更简单的例子:

interface ISomething
{
}
class OtherThing
{
}

然后说(方法内部):

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

效果很好。编译器不知道OtherThing是否可能是ISomething,所以当我们说它会成功时,它相信我们。但是,如果我们将OtherThing更改为密封类型(即sealed class OtherThing { }struct OtherThing { }),则不再允许强制转换。编译器知道它不太好(除非otnull,但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
}