密封关键字影响编译器';他对演员阵容的看法
本文关键字:看法 影响 关键字 编译器 密封 | 更新日期: 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
。
因为FooGetter
是IFoo<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 { }
),则不再允许强制转换。编译器知道它不太好(除非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
}