Ger*_*old 23 c# generics inheritance
鉴于这个高度简化的例子:
abstract class Animal { }
class Dog : Animal
{
public void Bark() { }
}
class Cat : Animal
{
public void Mew() { }
}
class SoundRecorder<T> where T : Animal
{
private readonly T _animal;
public SoundRecorder(T animal) { _animal = animal; }
public void RecordSound(string fact)
{
if (this._animal is Dog)
{
((Dog)this._animal).Bark(); // Compiler: Cannot convert type 'T' to 'Dog'.
((Dog)(Animal)this._animal).Bark(); // Compiles OK
}
}
}
Run Code Online (Sandbox Code Playgroud)
为什么编译器会抱怨单一类型转换(Dog)this._animal?我只是不明白为什么编译器似乎需要做两个演员的帮助._animal什么不能比一个Animal,可以吗?
当然,这个问题是由一个现实生活中的例子推动的,我必须修改现有代码,使得类似的强制转换是最方便的方式,而不需要重构整个批次.(是的,使用组合而不是继承;)).
Jon*_*eet 32
编辑:这是对Polity的回答的尝试重述 - 我 想我知道他想说的是什么,但我可能是错的.
我的原始答案(在线下)仍然在某些方面是规范的:编译器拒绝它,因为语言规范说它必须:)但是,试图猜测 语言设计者的观点(我从来没有C#设计委员会的一部分,我认为我没有问过他们,所以这真的是猜测......)这里......
我们习惯于在"编译时"或"执行时"考虑转换的有效性.通常,隐式转换是编译时保证有效的转换:
string x = "foo";
object y = x;
Run Code Online (Sandbox Code Playgroud)
这不会出错,所以这是隐含的.如果有什么可以去错了,语言的设计,使你必须告诉编译器,"相信我,我相信它会在执行时工作,即使你现在不能保证这一点." 显然,无论如何都会检查执行时间,但是你基本上告诉编译器你知道你在做什么:
object x = "foo";
string y = (string) x;
Run Code Online (Sandbox Code Playgroud)
现在,编译器已经阻止您尝试它认为不能转换工作1在一个有用的方法:
string x = "foo";
Guid y = (Guid) x;
Run Code Online (Sandbox Code Playgroud)
编译器知道没有转换string为Guid,所以编译器不相信你的抗议,你知道你正在做什么:你显然不是2.
所以这些是"编译时间"与"执行时间"检查的简单情况.但是仿制药呢?考虑这种方法:
public Stream ConvertToStream<T>(T value)
{
return (Stream) value;
}
Run Code Online (Sandbox Code Playgroud)
编译器知道什么?这里我们有两件可以变化的东西:值(当然在执行时变化)和类型参数T,它在可能不同的编译时指定.(我在这里忽略了反射,甚至T只在执行时才知道.)我们可以稍后编译调用代码,如下所示:
ConvertToStream<string>(value);
Run Code Online (Sandbox Code Playgroud)
在这一点上,如果更换类型参数的方法没有任何意义T有string,你最终与代码不会
编译:
// After type substitution
public Stream ConvertToStream(string value)
{
// Invalid
return (Stream) value;
}
Run Code Online (Sandbox Code Playgroud)
(泛型并不真正通过这种类型的替换和重新编译来工作,这会影响重载等 - 但它 有时可能是一种有用的思考方式.)
编译器在编译调用时无法报告- 调用不违反任何约束T,并且方法的主体应被视为实现细节.因此,如果编译器想要阻止以引入非感知转换的方式调用该方法,则必须在编译方法本身时执行此操作.
现在编译器/语言在这种方法中并不总是一致的.例如,考虑对泛型方法的这种更改,以及"使用T=string"版本调用时的"跟随类型替换":
// Valid
public Stream ConvertToStream<T>(T value)
{
return value as Stream;
}
// Invalid
public Stream ConvertToStream(string value)
{
return value as Stream;
}
Run Code Online (Sandbox Code Playgroud)
此代码不会编译通用的形式,即使取代后的版本没有.所以也许有更深层次的原因.也许在某些情况下,根本就没有合适的IL代表转换 - 而更容易的案例不值得使语言更复杂......
1它有时会这个"错误"的,在有些时候,转换为在CLR有效,但不是在C#中,如int[]
到uint[].我暂时会忽略这些边缘情况.
2向在这个答案中不喜欢编译器的拟人化的人道歉.显然编译器并没有真正对开发人员有任何情绪化的看法,但我相信它有助于理解这一点.
简单的答案是编译器抱怨,因为语言规范说它必须.规则在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();
}
Run Code Online (Sandbox Code Playgroud)
无论如何,我认为这只是清洁工,只进行一次转换.
Pol*_*ity 13
问题是编译器不能保证_animal可以转换为Dog,因为你给SoundRecorded类型参数的唯一限制是类型应该是Animal或者继承自Animal.所以编译器实际上在想:如果你构造一个SoundRecorder<Cat>,那么转换操作就是无效的.
不幸的是,编译器不够聪明,无法通过事先进行'is'检查来确保您安全地保护您的代码不会到达那里.
如果您将给定的动物存储为实际动物,则这不会成为问题,因为编译器始终允许从基本类型到派生类型的任何转换.编译器不允许从Dog到Cat的转换
编辑请参阅Jon Skeets的答案以获得更具体的解释.
| 归档时间: |
|
| 查看次数: |
16261 次 |
| 最近记录: |