编辑:这是对Polit的回答的一次尝试性重述——我认为我知道他想说什么,但我可能错了。
我的原始答案(在下面)某种程度上仍然是标准答案:编译器拒绝它是因为语言规范要求如此 :) 但是,为了猜测语言设计者的观点(我从未参加过C#设计委员会,我也不认为我向他们询问过这个问题,所以这确实是猜测......),以下是......
我们习惯于考虑“在编译时”或“在执行时”的转换有效性。通常情况下,隐式转换是编译时保证有效的转换:
string x = "foo"
object y = x
如果某些事情不可能出错,那么它是隐式的。如果某些事情可能出错,语言的设计就要求你告诉编译器:“相信我,在执行时它会工作,尽管现在你不能保证。”显然,在执行时仍然有检查,但你基本上是告诉编译器你知道自己在做什么:
object x = "foo"
string y = (string) x
现在编译器已经可以防止您尝试转换,因为它认为这种转换在实际中无法起到有用的作用1:
string x = "foo";
Guid y = (Guid) x;
编译器知道从字符串到 GUID 没有转换,因此编译器不相信你执意认为自己知道在做什么:显然你不知道。这是“编译时”和“运行时”检查的简单情况。但泛型呢?考虑下面的方法:
public Stream ConvertToStream<T>(T value)
{
return (Stream) value;
}
编译器知道什么?我们有两个可能变化的东西:
- 值(当然是在执行时变化)
- 类型参数
T
,它在可能不同的编译时间指定。(这里忽略反射,即使T
也只有在执行时才知道。)我们可以稍后编译调用代码,就像这样:
ConvertToStream<string>(value);
如果您将类型参数T
替换为string
,那么此时该方法就没有意义了,因为最终生成的代码是无法编译的:
public Stream ConvertToStream(string value)
{
return (Stream) value;
}
泛型并不是通过进行这种类型替换和重新编译来工作的,这会影响到重载等 - 但有时这种想法是有帮助的。
编译器无法在编译调用时报告这个问题 - 调用不违反对T
的任何约束,并且方法体应视为实现细节。因此,如果编译器想要防止以引入非意义转换的方式调用该方法,它必须在方法本身被编译时这样做。
现在,编译器/语言在这种方法上并不总是一致的。例如,考虑对泛型方法的此更改和使用T=string
调用时的“以下类型替换”版本:
public Stream ConvertToStream<T>(T value)
{
return value as Stream;
}
public Stream ConvertToStream(string value)
{
return value as Stream;
}
这段代码在通用形式下是可以编译的,尽管类型替换后的版本不行。所以可能有更深层次的原因。也许在某些情况下,根本没有合适的IL来表示转换 - 而更容易的情况并不值得让语言变得更加复杂...
有时它会出现"错误",因为有时候CLR中的转换是有效的,但在C#中却无效,例如int[]到uint[]。此处暂且忽略这些边缘情况。
对于那些不喜欢在这个答案中拟人化编译器的人表示歉意。显然,编译器实际上没有任何关于开发者的情感观点,但我相信这有助于传达观点。
简单地说,编译器抱怨是因为语言规范要求它这样做。规则在C#4规范的第6.2.7节中给出。
以下显式转换存在于给定类型参数T中:
从类型参数U到T,前提是T依赖于U。(见10.1.5节。)
这里Dog不依赖于T,因此不允许进行转换。
我怀疑这个规则的存在是为了避免一些晦涩的角落情况 - 在这种情况下,当你可以逻辑上看到它应该是一个有效的转换尝试时,这有点麻烦,但我认为将这种逻辑编码化会使语言更加复杂。
请注意,另一种选择可能是使用as而不是is-then-cast:
Dog dog = this._animal as Dog;
if (dog != null)
{
dog.Bark();
}
我认为这样更清晰,因为只需要进行一次转换。
SoundRecorder
方法移动到接口中,然后让两个类都实现它呢?这样你就可以轻松地调用它了! - Ahmed Magdy