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”:
…
IfE
does not already have the typeSₓ
, then a standard explicit conversion fromE
toSₓ
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
d
to a typeNum
is processed as follows: -
If
d
has a type, letS
be that type.- let
Sᵢ
andTᵢ
beDecimal
andNum
- let
S₀
andT₀
beDecimal
andNum
- let
-
Find the set of types,
D
, from which user-defined conversion operators will be considered. This set consists ofDecimal
andNum
. -
If a standard implicit conversion exists from a type
long
to typeDecimal
, thenlong
is 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 byDecimal
toNum
.
U
ispublic 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
d
does not already have the typelong
, then a standard explicit conversion fromd
tolong
is 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
|