c# - 无法转换为 C# 中的基本泛型类型,但可以使用“as”运算符
问题描述
为什么强制转换不适用于受约束的泛型类型,如下所示?
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 时,您会得到一个与 int 具有相同值的 long ,而不是铸造错误。
对于引用类型,这是不正确的。在您的情况下,您可以有一个从 B 到 B 的自定义转换运算符,它实际上会在这种(T) new B()
情况下被调用,但不是G<B>
从 B1 到 B 的转换,因为and的具体泛型类型G<B1>
实际上是相同的。由于这是一个可以随时更改的实现细节,因此您确实希望避免混淆和潜在的行为变化。
推荐阅读
- java - 如何设置多语言文本转语音
- forms - 使用表单发布值并显示前端
- python-3.x - 读取 csv 文件时出错,逗号分隔文件大小 65MB
- scala - 使用返回 Future 的二元运算折叠序列
- java - PDF 保存在 Oracle DB 中:错误 - java.sql.SQLException:数据大小大于此类型的最大大小:71365
- node.js - Type Error: Cannot read property 'hasListCollectionsCommand' of null
- php - 在 laravel 选择查询中获取附加数据
- python - 是否有来自 mysql 的任何 python 代码允许我选择第一行观察结果?
- javascript - 在 React 中更新嵌套数组中的嵌套对象
- java - 休息端点异常处理