First off, let's have a look at Mr. Lippert's blog:
Chained user-defined explicit conversions in C#
The compiler will sometimes1 insert the explicit conversion for us:
- Part of the blogpost:
...
When a user-defined explicit cast requires an explicit conversion on either the call side or the return side, the compiler will insert the explicit conversions as needed.
The compiler figures that if the developer put the explicit cast in the code in the first place then the developer knew what they were doing and took the risk that any of the conversions might fail.
...
As this question, it's just one of the time of sometimes. What explicit conversion the compiler inserted is like we writing in the following code:
Test generic method with explicit conversion
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);
}
}
and the generated IL of the test method is:
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
Let's go one step back, to see the test of explicit operator as your question:
Test explicit operator
public partial class TestClass {
public static void TestExplicitOperator() {
double d=2.5;
Num<byte> b=(Num<byte>)d;
}
}
and you've already seen the IL before:
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
Do you notice that they are quite similar? The difference is that the parameter !0
is a generic parameter in a type definition of your original code, and !!0
in the generic method test, is generic parameter in a method definition. You might want to have a look at chapter §II.7.1
of the specification Standard ECMA-335.
However, the most point here, is they both get into the type <uint8>
(which is byte) of the generic definition; and as I mentioned above, according to Mr. Lippert's blogpost told us that the compiler sometimes inserts the explicit conversion when you did specify them explicitly!
Finally, as you suppose this is strange behavior of the compiler, and let me guess what is possibly you would think the compiler should do:
Test generic method by specifying type parameter:
public partial class TestClass {
public static void TestGenericMethodBySpecifyingTypeParameter() {
double d=2.5;
Num<byte> b=NumHelper.From<byte>(d);
}
}
Did I guess correctly? Anyway, what we are interested here, again, the IL. And I cannot wait to see the IL, it is:
Ooooops .. seem it's not as what the compiler thought an explicit operator would behaves like.
For conclution, when we specified the conversion explicitly, it's pretty semantic to say that we are expecting to convert one thing to another, the compiler deduced that and insert the obvious necessary convertion of the involved types; and once it found the type involved is not legal to be converted, it complains, just as we specified a more simply wrong conversion, such as (String)3.1415926 ..
.
Wish it's now more helpful without losing the correctness.
1: It's my personal expression of sometimes, in the blogpost actually is said as needed.
The following is some test for contrasting, when one possibly expect to convert the type with a existent explicit operator; and I put some comments in the code for describing each case:
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;
Maybe it's easier to understand.