views:

113

answers:

1

I have a struct like this, with an explicit conversion to float:

struct TwFix32
{
    public static explicit operator float(TwFix32 x) { ... }
}

I can convert a TwFix32 to int with a single explicit cast: (int)fix32

But to convert it to decimal, I have to use two casts: (decimal)(float)fix32

There is no implicit conversion from float to either int or decimal. Why does the compiler let me omit the intermediate cast to float when I'm going to int, but not when I'm going to decimal?

+8  A: 

I am often at a loss to give a satisfactory answer to "why" questions.

The reason that the C# compiler exhibits that behaviour is because it is (in this case at least (*)) a correct implementation of the C# specification.

Section 6.4.5 of the specification describes how user-defined conversions are analyzed. A careful reading of that section will explain why the explicit conversion to int is legal but to decimal is not.

Specifically, the relevant paragraph is:

Find the set of applicable user-defined and lifted conversion operators, U. This set consists of the user-defined and lifted implicit or explicit conversion operators declared by the classes or structs in D that convert from a type encompassing or encompassed by S to a type encompassing or encompassed by T. If U is empty, the conversion is undefined and a compile-time error occurs.

In your case, S is TwFix and T is either int or decimal. The only user-defined explicit conversion on TwFix returns a float. int is encompassed by float, but decimal is neither encompassed by nor encompasses float. Therefore the set U has a member in one case and is empty in the other. Therefore one case produces an error, as the specification says to do, and the other does not.

I have the feeling that this answer is not satisfactory. If not, can you rephrase the question so that it doesn't have the word "why" in it? I'm a lot better at answering "what" or "how" questions than "why" questions.

(*) The compiler has known bugs in the code which computes whether one type encompasses another for the purpose of determining what built-in conversions are relevant when analyzing the semantics of a particular user-defined conversion. In many cases we're deliberately not fixing these bugs because doing so would introduce a breaking change in real-world code for no great benefit. I would like very much to revisit this section of the specification and rewrite it so as to remove the concept of "encompassing type"; it's a bit of an oddity in the spec. And, as you've discovered, it produces this oddity, where float is explicitly convertible to decimal and decimal is explicitly convertible to float, but since neither encompasses the other, the user-defined explicit conversion code doesn't like it. However this is very low priority.

Eric Lippert
My feeling is that the underlying question is why (yeah, I know) the fact that there's an implicit conversion from int to float should affect conversions which occur in the opposite direction (TwFix32 to float to int). This feels to me like it *should* be an irrelevant conversion in this case - but it's clearly not, because it affects encompassing. I suspect the *real* "why" answer here is likely to be, "Because it makes many other cases work as expected, at the cost of this sort of oddity."
Jon Skeet
@Jon: Correct; if you think about these rules from the perspective of analyzing user-defined conversions on *reference* types it makes more sense; non-interface reference types typically don't have situations like the float/decimal situation.
Eric Lippert