generics:铸造和价值类型,为什么这是非法的?

为什么这是编译时错误?

public TCastTo CastMe(TSource i) { return (TCastTo)i; } 

错误:

annot将类型’TSource’转换为’TCastTo’

为什么这是一个运行时错误?

 public TCastTo CastMe(TSource i) { return (TCastTo)(object)i; } int a = 4; long b = CastMe(a); // InvalidCastException // this contrived example works int aa = 4; int bb = CastMe(aa); // this also works, the problem is limited to value types string s = "foo"; object o = CastMe(s); 

我搜索了SO和互联网以获得答案,并找到了类似的通用相关铸造问题的许多解释,但我找不到任何关于这个特殊的简单案例。

为什么这是编译时错误?

问题在于,每种可能的值类型组合对于强制转换的含义都有不同的规则。 将64位双精度转换为16位int与将十进制转换为浮点数完全不同,依此类推。 可能性的数量是巨大的。 所以想像编译器一样。 编译器应该为您的程序生成什么代码?

编译器必须生成在运行时再次启动编译器的代码, 对类型进行全新分析,并动态发出适当的代码

这看起来似乎可能比你预期的generics更多的工作和更少的性能,所以我们简单地禁止它。 如果您真正想要的是编译器再次启动并对类型进行分析,请在C#4中使用“dynamic”; 这就是它的作用。

为什么这是一个运行时错误?

同样的道理。

由于与上述相同的原因,盒装的int只能解包到int(或int?); 如果CLR试图从盒装值类型到每个其他可能的值类型进行所有可能的转换,那么基本上它必须在运行时再次运行编译器 。 那会非常慢。

那么为什么它不是参考类型的错误?

因为每个引用类型转换与每个其他引用类型转换相同 :您询问对象以查看它是从所需类型派生还是与所需类型相同。 如果不是,则抛出exception(如果进行强制转换)或结果为null / false(如果使用“as / is”运算符)。 规则对于引用类型是一致的,它们不是值类型。 记住引用类型知道自己的类型 。 价值类型没有; 对于值类型, 执行存储的变量是唯一知道适用于这些位的类型语义的东西 。 值类型包含其值,不包含其他信息 。 引用类型包含其值以及大量额外数据。

有关更多信息,请参阅我关于此主题的文章:

http://ericlippert.com/2009/03/03/representation-and-identity/

C#对多个不同的底层操作使用一种强制转换语法:

  • 向上转型
  • 沮丧
  • 拳击
  • 拆箱
  • 数字转换
  • 用户定义的转换

在通用上下文中,编译器无法知道哪些是正确的,并且它们都生成不同的MSIL,因此它会失败。

通过写return (TCastTo)(object)i; 相反,您强制编译器对object执行向上转换,然后向下转换为TCastTo 。 编译器将生成代码,但如果这不是转换相关类型的正确方法,则会出现运行时错误。


代码示例:

 public static class DefaultConverter { private static Converter cached; static DefaultConverter() { ParameterExpression p = Expression.Parameter(typeof(TSource)); cached = Expression.Lambda(Expression.Convert(p, typeof(TCastTo), p).Compile(); } public static Converter Instance { return cached; } } public static class DefaultConverter { public static TOutput ConvertBen(TInput from) { return DefaultConverter.Instance.Invoke(from); } public static TOutput ConvertEric(dynamic from) { return from; } } 

埃里克的方式肯定会更短,但我认为我应该更快。

导致编译错误,因为无法将TSource隐式强制转换为TCastTo。 这两种类型可以在其inheritance树上共享一个分支,但不能保证。 如果您只想调用共享祖先的类型,则应修改CastMe()签名以使用祖先类型而不是generics。

运行时错误示例通过首先将TSource i强制转换为对象来避免第一个示例中的错误,这是C#中所有对象派生自的对象。 虽然编译器没有抱怨(因为对象 – >从它派生的东西,可能是有效的),如果转换无效,则抛出via(Type)变量语法的行为将抛出。 (编译器阻止在示例1中发生的相同问题)。

另一个解决方案,它与你正在寻找的东西类似……

  public static T2 CastTo(T input, Func convert) { return convert(input); } 

你会这样称呼它。

 int a = 314; long b = CastTo(a, i=>(long)i); 

希望这会有所帮助。