首先,让我们看看 Lippert 先生的博客:
C# 中的链式用户定义显式转换
编译器有时会为我们插入显式转换:
- 部分博文:
...
当用户定义的显式转换需要在调用端或返回端进行显式转换时,编译器将根据需要插入显式转换。
编译器认为,如果开发人员首先将显式转换放在代码中,那么开发人员就知道他们在做什么,并冒着任何转换都可能失败的风险。
...
作为这个问题,它只是有时的时间之一。编译器插入的显式转换就像我们在以下代码中编写的那样:
使用显式转换测试泛型方法
public static class NumHelper {
public static Num<T> From<T>(T value) {
return new Num<T>(value);
}
}
public partial class TestClass {
public static void TestGenericMethodWithExplicitConversion() {
double d=2.5;
Num<byte> b=NumHelper.From((byte)d);
}
}
测试方法生成的IL为:
IL_0000: nop
IL_0001: ldc.r8 2.5
IL_000a: stloc.0
IL_000b: ldloc.0
IL_000c: conv.u1
IL_000d: call valuetype Num`1<!!0> NumHelper::From<uint8>(!!0)
IL_0012: stloc.1
IL_0013: ret
让我们退后一步,将显式运算符的测试视为您的问题:
测试显式运算符
public partial class TestClass {
public static void TestExplicitOperator() {
double d=2.5;
Num<byte> b=(Num<byte>)d;
}
}
并且您之前已经看过 IL:
IL_0000: nop
IL_0001: ldc.r8 2.5
IL_000a: stloc.0
IL_000b: ldloc.0
IL_000c: conv.u1
IL_000d: call valuetype Num`1<!0> valuetype Num`1<uint8>::op_Explicit(!0)
IL_0012: stloc.1
IL_0013: ret
你注意到它们非常相似吗?不同之处在于参数!0
是您原始代码的类型定义中的泛型参数!!0
,而在泛型方法测试中,是方法定义中的泛型参数。您可能想查看§II.7.1
规范标准 ECMA-335的章节。
但是,这里最重要的是,它们都进入<uint8>
了泛型定义的类型(即字节);正如我上面提到的,根据 Lippert 先生的博文告诉我们,编译器有时会在您明确指定它们时插入显式转换!
最后,您认为这是编译器的奇怪行为,让我猜猜您认为编译器应该做什么:
我猜对了吗?无论如何,我们在这里再次感兴趣的是 IL。我迫不及待地想看到 IL,它是:
Ooooops .. 似乎不像编译器认为显式运算符的行为那样。
最后,当我们明确指定转换时,说我们期望将一件事转换为另一件事是非常语义化的,编译器推断出这一点并插入所涉及类型的明显必要的转换;一旦它发现所涉及的类型不能被转换,它就会抱怨,就像我们指定了一个更简单的错误转换一样,例如(String)3.1415926 ..
.
希望它现在更有帮助而不会失去正确性。
1 :有时是我个人的表达,在博文中其实是根据需要说的。
以下是一些对比测试,当人们可能期望使用现有的显式运算符转换类型时;我在代码中添加了一些注释来描述每种情况:
double d=2.5;
Num<byte> b=(Num<byte>)d; // explicitly
byte x=(byte)d; // explicitly, as the case above
Num<byte> y=d; // no explicit, and won't compile
// d can be `IConvertible`, compiles
Num<IConvertible> c=(Num<IConvertible>)d;
// d can be `IConvertible`;
// but the conversion operator is explicit, requires specified explicitly
Num<IConvertible> e=d;
// d cannot be `String`, won't compile even specified explicitly
Num<String> s=(Num<String>)d;
// as the case above, won't compile even specified explicitly
String t=(String)d;
也许更容易理解。