[英]Cannot cast to base generic type in C# but can use 'as' operator
为什么强制转换不适用于约束泛型类型,如下所示?
class B { }
class B1 : B { }
class G<T> where T : B
{
void x()
{
T b1 = new B1(); // why implicit conversion doesn't compile?
T b2 = (T)new B1(); // why explicit conversion doesn't compile either?
T b3 = new B1() as T; // this works!
}
}
B1
不限于可分配给 T。例如:
void Main()
{
new G<C1>().x();
}
class B { }
class B1 : B { }
class C1 : B { }
class G<T> where T : B
{
public void x()
{
T b3 = new B1() as T;
b3.Dump(); // null, because B1 cannot be converted to C1
}
}
仅仅因为 T 被限制为 B 并不意味着您可以将 B 的任何后代强制转换为任何可能的 T。
为什么不允许呢? 在我看来,这没有任何意义。 如果您想确保 B1 可分配给 T,则不应使用泛型。 犯这种错误太容易了,如果可能,您应该首先避免强制转换泛型。 它们(主要)是为了使静态类型更强大,同时保持类型安全(和性能优势)。
但是,肯定有明显错误的情况不会被抓住,因为
T b2 = (T)new B();
确实编译,即使它实际上有同样的问题,你会得到一个运行时错误铸如果T是不是B.
当然,在这种情况下,检查C# 规范会很有帮助,而且很清楚,它说:
上述规则不允许从不受约束的类型参数直接显式转换为非接口类型,这可能令人惊讶。 这条规则的原因是为了防止混淆并使此类转换的语义清晰。
虽然这似乎只对值类型有意义,但这解释了为什么你可以直接执行(T)new B();
,以及为什么你不能做(T)new B1();
- 即使两者都有相同的问题,T 不一定是 B。
请记住,C#中的运算符不是虚拟的- 它们取决于表达式的编译时类型。 对于值类型参数,您实际上会为您使用的每个值类型获得一个变体(即List<long>
使用与List<int>
不同的代码) - 因此您会获得正确的转换,例如从 int 转换为 long 时,您会得到一个long 与 int 具有相同的值,而不是转换错误。
对于引用类型,这不是真的。 在你的情况下,你可以有一个从 B 到 B 的自定义转换运算符,它实际上会在(T) new B()
情况下被调用,但不是从 B1 到 B 的转换,因为G<B>
的具体化泛型类型和G<B1>
其实是一样的。 由于这是一个可以随时更改的实现细节,您确实希望避免混淆和潜在的行为变化。
T b1 = new B1(); // why implicit conversion doesn't compile?
仅仅因为 T 和 B1 都源自 B 并不意味着 B1 可以赋值给 T。它们都可以赋值给 B,所以
B b1 = new B1(); // should work
.
T b2 = (T)new B1(); // why explicit conversion doesn't compile either?
如上所述,具有公共基类的两个类不能确保类型兼容性,因此显式转换也不起作用
T b3 = new B1() as T; // this works!
它有效,但您可能将 null 分配给 b3,因为 B1 不能转换为 T。 as 运算符只返回 null 而不是发出编译器警告或抛出异常。
答案是,因为不能保证你提供B1
作为泛型参数,你不能用泛型来做到这一点,让我们探讨一下为什么......
给定的
class Animal { }
class Dog : Animal { }
class Cat : Animal { }
class Something<T> where T : Animal
{
public T Animal {get;set;}
void x()
{
Animal = (T)new Cat();
}
}
现在,如果你像这样使用你的类怎么办
var dog = new Something<Dog>();
dog.X() // internally you are trying to cast a Cat to a Dog
Dog dog = dog.Animal; // now you just tried to mash a Cat into a dog
Dog.Bark() // what the...
约束是一个最低限度的契约,就是这样。 它们允许您在指定的合约上使用泛型参数。
这和你为什么不能做下面的事情完全一样
Dog dog = new Cat();
即使它们都继承自Animal
,也不意味着它们是相同的......它们内部具有不同的内存布局,它们具有不同的方法和属性,它们不能像这样静态类型混合在一起。
简而言之,您可能需要重新考虑您的问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.