The following C# program silently and implicitly calls an explicit decimal-to-long conversion operator, losing precision.
I don’t understand why this happens. As far as I understand, in C# explicit operator should not be called implicitly by the language. Especially that in this case the silent explicit conversion is losing precision (1.1M => 1L).
This odd behavior actually caused a bug in my program.
Here is the simplified code:
// Custom number class
struct Num
{
long Raw;
public static implicit operator Num(long v) => new Num { Raw = v };
}
class Program
{
static void Main()
{
decimal d = 1.1m;
// The following line implicitly converts d to long (silently losing precision),
// then calls Num.op_Implicit(long)
Num num = (Num)d; // <=== should not compile???
}
}
Here is the resulting IL. You can see that System.Decimal::op_Explicit is called, even though it’s never asked for.
IL_0000: ldc.i4.s 11
IL_0002: ldc.i4.0
IL_0003: ldc.i4.0
IL_0004: ldc.i4.0
IL_0005: ldc.i4.1
IL_0006: newobj instance void [mscorlib]System.Decimal::.ctor(int32, int32, int32, bool, uint8)
IL_000b: stloc.0
IL_000c: ldloc.0
IL_000d: call int64 [mscorlib]System.Decimal::op_Explicit(valuetype [mscorlib]System.Decimal) // <=== ???
IL_0012: call valuetype Num Num::op_Implicit(int64)
IL_0017: stloc.1
IL_0018: ret
1
4 Answers
4
Reset to default
Highest score (default)
Trending (recent votes count more)
Date modified (newest first)
Date created (oldest first)
The rules for user defined explicit conversions are given in the C# spec. 10.5.5 User-defined explicit conversions. The rules are quite complex, but specifically allow for additional explicit conversions to be performed “implicitly”:
…
IfEdoes not already have the typeSₓ, then a standard explicit conversion fromEtoSₓis performed.
…
(Here E is the source type – decimal and Sₓ is the type your conversion operator converts from – long).
Note that you are performing an explicit conversion to Num (even though it uses the implicit operator you have defined), so the rules for explicit conversions are followed, which allows the extra “implicit” explicit conversion from decimal to long.
I’d say that Num num = (Num)d; is very, very explicit because (Num) is put right in front of d.
It looks like the compiler sees you want an explicit cast, and then it does (Num)(long)d because d is a decimal and (Num) enables it to "force" a long out of the decimal. I have to be honest that I’m guessing, but it could be an explanation.
The following would be implicit, in my (not so expert) opinion:
Num num = d;
And indeed that does not compile with the given struct.
3
-
Yes, what else can be more explicit than a direct explict cast
(Num)– Dmitry Bychenko15 hours ago
-
1
You misunderstood the question. The implicit conversion to long is called, not to Num.
– kaalus15 hours ago
-
I updated my answer to give a possible explanation.
– Peter B14 hours ago
The rules for explicit conversion are incredibly complex in C# (wart).
The C# draft specification section that explains how a user-defined explicit conversion is called has a lot of sub-cases for different type possiblities. Note that the specification hasn’t been updated, and seems to have some errors / be inconsistent.
In your example, I think these are the rules that apply:
-
A user-defined explicit conversion from an expression
dto a typeNumis processed as follows: -
If
dhas a type, letSbe that type.- let
SᵢandTᵢbeDecimalandNum - let
S₀andT₀beDecimalandNum
- let
-
Find the set of types,
D, from which user-defined conversion operators will be considered. This set consists ofDecimalandNum. -
If a standard implicit conversion exists from a type
longto typeDecimal, thenlongis said to be encompassed byDecimal. -
Find the set of applicable user-defined conversion operators,
U. This set consists of the user-defined explicit conversion operators declared by the structs in {Decimal,Num} that convert from a type encompassed byDecimaltoNum.
Uispublic static implicit operator Num(long v)
-
Find the most-specific source type,
Sₓ, of the operators inU:Sₓis the most-encompassing type in the combined set of source types of the operators inU
Sₓislong -
If
ddoes not already have the typelong, then a standard explicit conversion fromdtolongis performed.
The C# reference documents built-in numeric conversions. Conversion from decimal to long is one of these conversions and that this will be performed using an explicit numeric conversion. It is valid C# syntax, and will compile. The reference highlights the fact that data loss is possible. It also states that the conversion uses rounding.
While this behaviour is not what you desire: it is specified in the official documentation. While data loss is possible, the alternative would be to disallow it, which would be harsh restriction. The language designers had to make a call, and chose the possibility of data loss instead of disallowing it.
Your Answer
Sign up or log in
Post as a guest
Required, but never shown
Post as a guest
Required, but never shown
By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.
Not the answer you're looking for? Browse other questions tagged
or ask your own question.
or ask your own question.
Num num = d;, now it is implicit.2 hours ago
|