Is this is an ExpressionTrees bug? #2 - c#

Looks like ExpressionTrees compiler should be near with the C# spec in many behaviors, but unlike C# there is no support for conversion from decimal to any enum-type:
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
Func<decimal, ConsoleColor> converter1 = x => (ConsoleColor) x;
ConsoleColor c1 = converter1(7m); // fine
Expression<Func<decimal, ConsoleColor>> expr = x => (ConsoleColor) x;
// System.InvalidOperationException was unhandled
// No coercion operator is defined between types
// 'System.Decimal' and 'System.ConsoleColor'.
Func<decimal, ConsoleColor> converter2 = expr.Compile();
ConsoleColor c2 = converter2(7m);
}
}
Other rarely used C# explicit conversions, like double -> enum-type exists and works as explained in C# specification, but not decimal -> enum-type. Is this a bug?

It is probably a bug, and it is probably my fault. Sorry about that.
Getting decimal conversions right was one of the hardest parts of building the expression tree code correct in the compiler and the runtime because decimal conversions are actually implemented as user-defined conversions in the runtime, but treated as built-in conversions by the compiler. Decimal is the only type with this property, and therefore there are all kinds of special-purpose gear in the analyzer for these cases. In fact, there is a method called IsEnumToDecimalConversion in the analyzer to handle the special case of nullable enum to nullable decimal conversion; quite a complex special case.
Odds are good that I failed to consider some case going the other way, and generated bad code as a result. Thanks for the note; I'll send this off to the test team, and we'll see if we can get a repro going. Odds are good that if this does turn out to be a bona fide bug, this will not be fixed for C# 4 initial release; at this point we are taking only "user is electrocuted by the compiler" bugs so that the release is stable.

Not a real answer yet, I'm investigating, but the first line is compiled as:
Func<decimal, ConsoleColor> converter1 = x => (ConsoleColor)(int)x;
If you try to create an expression from the previous lambda, it will work.
EDIT : In the C# spec, §6.2.2, you can read:
An explicit enumeration conversion
between two types is processed by
treating any participating enum-type
as the underlying type of that
enum-type, and then performing an
implicit or explicit numeric
conversion between the resulting
types. For example, given an enum-type
E with and underlying type of int, a
conversion from E to byte is processed
as an explicit numeric conversion
(§6.2.1) from int to byte, and a
conversion from byte to E is processed
as an implicit numeric conversion
(§6.1.2) from byte to int.
So explicit casts from enum to decimal are handled specifically, that's why you get the nested casts (int then decimal). But I can't see why the compiler doesn't parse the lambda body the same way in both cases.

Related

C# Compiler bug: allows for conversion from Nullable<decimal> to decimal

Consider following code:
public class DecimalWrapper
{
public static implicit operator DecimalWrapper(decimal x) => new();
}
[Fact]
public void Test()
{
// Why this even compiles? Since there is no implicit conversion from decimal? -> decimal
DecimalWrapper c = (decimal?)null; // variable 'c' is null!
}
I would not expect it to compile at all, since there is no implicit conversion from decimal? to decimal.
I consider it a bug or do I get something wrong?
I have seen this:
Serious bugs with lifted/nullable conversions from int, allowing conversion from decimal
but this looks different and it is old (7+ years), so the bugs should be fixed by now, but cannot be sure since all the links to bug reports are gone)... :(
I would really like to use such code in real solution (tracking of calculations), but this prevents me from it.
PS: I am compiling on Windows.
According to specification lifted conversion operators are allowed only for value type to value type conversions (and their nullable counterparts) but in the Roslyn compiler source code you can find next comment:
DELIBERATE SPEC VIOLATION:
The native compiler allows for a "lifted" conversion even when the return type of the conversion not a non-nullable value type. For example, if we have a conversion from struct S to string, then a "lifted" conversion from S? to string is considered by the native compiler to exist, with the semantics of "s.HasValue ? (string)s.Value : (string)null". The Roslyn compiler perpetuates this error for the sake of backwards compatibility.
So it seems to be an actuall bug which was introduced for backwards compatibility. And the decompilation shows exactly that behaviour.

Can anyone explain this C# quirk? [duplicate]

short s;
s = (EitherTrueOrFalse()) ? 0 : 1;
This fails with:
error CS0266: Cannot implicitly
convert type 'int' to 'short'. An
explicit conversion exists (are you
missing a cast?)
Can anyone explain why this is so? The only thing I can think of is that the compiler doesn't look at the second value and doesn't know the range between the two, in the case I wrote something like
short s;
s = (EitherTrueOrFalse()) ? 0 : 65000;
Correct?
The only fix is with an ugly cast?
Also, it seems C# does not have a type suffix for the short type. That's a pretty grave oversight IMO. Otherwise, that would've been a solution...
The compiler has an implicit conversion from a constant expression to various primitive types (so long as the value is within the appropriate range), but here the expression isn't constant - it's just an int expression. It's pretty much the same as:
short s;
s = CallSomeMethodReturningInt32();
as far as the compiler is concerned.
There are two options - you could cast the whole expression, or cast each of the latter two operands:
short s = (EitherTrueOrFalse()) ? (short) 0 : (short) 1;
to make the overall expression type short. In this particular case, it's a pity that there isn't a numeric literal suffix to explicitly declare a short literal. Apparently the language designers did consider this, but felt it was a relatively rare situation. (I think I'd probably agree.)
The part about implicit constant conversions is from the C# 3.0 spec section 6.1.8:
6.1.8 Implicit constant expression conversions
An implicit constant
expression conversion permits the
following conversions:
A constant-expression (§7.18) of type
int can be converted to type sbyte,
byte, short, ushort, uint, or ulong,
provided the value of the
constant-expression is within the
range of the destination type.
A
constant-expression of type long can
be converted to type ulong, provided
the value of the constant-expression
is not negative.
Because the cast is done by the compiler, not at runtime, I wouldn't call it an ugly cast, I would call it a complicated syntax:
s = (EitherTrueOrFalse()) ? (short)0 : (short)1;
I mean, this is the way it is written in C#, even if it looks ugly.
See this blog article.
See Marc Gravell's answer on that question.
I guess this has the same reason as this won't compile:
short s1 = GetShort1();
short s2 = GetShort2();
short s3 = s1 + s2;
I.e. that whenever short is used for something, it gets promoted to int.

Difference between casting and using the Convert.To() method

I have a function that casts a double on string values.
string variable = "5.00";
double varDouble = (double)variable;
A code change was checked in and the project builds with the error: System.InvalidCastException: Specified cast is not valid.
However, after doing the following...
string variable = "5.00";
double varDouble = Convert.ToDouble(variable);
...the project builds without any errors.
What is the difference between casting and using the Convert.To() method? Why does casting throw an Exception and using the Convert.To() does not?
Even if you may see them somehow as equivalent they're completely different in purpose. Let's first try to define what a cast is:
Casting is the action of changing an entity of one data type into another.
It's a little bit generic and it's somehow equivalent to a conversion because a cast often has the same syntax of a conversion so the question should be when a cast (implicit or explicit) is allowed by the language and when do you have to use a (more) explicit conversion?
Let me first draw a simple line between them. Formally (even if equivalent for language syntax) a cast will change the type while a conversion will/may change the value (eventually together with the type). Also a cast is reversible while a conversion may not be.
This topic is pretty vast so let's try to narrow it a little bit by excluding custom cast operators from the game.
Implicit casts
In C# a cast is implicit when you won't lose any information (please note that this check is performed with types and not with their actual values).
Primitive types
For example:
int tinyInteger = 10;
long bigInteger = tinyInteger;
float tinyReal = 10.0f;
double bigReal = tinyReal;
These casts are implicit because during the conversion you won't lose any information (you just make the type wider). Vice versa implicit cast isn't allowed because, regardless of their actual values (because they can be checked only at run-time), during the conversion you may lose some information. For example this code won't compile because a double may contain (and actually it does) a value not representable with a float:
// won't compile!
double bigReal = Double.MaxValue;
float tinyReal = bigReal;
Objects
In case of an object (a pointer to) the cast is always implicit when the compiler can be sure that the source type is a derived class (or it implements) the type of the target class, for example:
string text = "123";
IFormattable formattable = text;
NotSupportedException derivedException = new NotSupportedException();
Exception baseException = derivedException;
In this case the compiler knows that string implements IFormattable and that NotSupportedException is (derives from) Exception so the cast is implicit. No information is lost because objects don't change their types (this is different with structs and primitive types because with a cast you create a new object of another type), what changes is your view of them.
Explicit casts
A cast is explicit when the conversion isn't done implicitly by the compiler and then you must use the cast operator. Usually it means that:
You may lose information or data so you have to be aware of it.
The conversion may fail (because you can't convert one type to the other) so, again, you must be aware of what you're doing.
Primitive types
An explicit cast is required for primitive types when during the conversion you may lose some data, for example:
double precise = Math.Cos(Math.PI * 1.23456) / Math.Sin(1.23456);
float coarse = (float)precise;
float epsilon = (float)Double.Epsilon;
In both examples, even if the values fall within the float range, you'll lose information (in this case precision) so the conversion must be explicit. Now try this:
float max = (float)Double.MaxValue;
This conversion will fail so, again, it must be explicit so you're aware of it and you may do a check (in the example the value is constant but it may come from some run-time computations or I/O). Back to your example:
// won't compile!
string text = "123";
double value = (double)text;
This won't compile because the compiler can't convert text to numbers. Text may contain any characters, not numbers only and this is too much, in C#, even for an explicit cast (but it may be allowed in another language).
Objects
Conversions from pointers (to objects) may fail if the types are unrelated, for example this code won't compile (because the compiler knows there is no possible conversion):
// won't compile!
string text = (string)AppDomain.Current;
Exception exception = (Exception)"abc";
This code will compile but it may fail at run-time (it depends on the effective type of casted objects) with an InvalidCastException:
object obj = GetNextObjectFromInput();
string text = (string)obj;
obj = GetNextObjectFromInput();
Exception exception = (Exception)obj;
Conversions
So, finally, if casts are conversions then why do we need classes like Convert? Ignoring the subtle differences that come from Convert implementation and IConvertible implementations actually because in C# with a cast you say to the compiler:
trust me, this type is that type even if you can't know it now, let me do it and you'll see.
-or-
don't worry, I don't care if something will be lost in this conversion.
For anything else a more explicit operation is needed (think about implications of easy casts, that's why C++ introduced long, verbose and explicit syntax for them). This may involve a complex operation (for string -> double conversion a parsing will be needed). A conversion to string, for example, is always possible (via ToString() method) but it may mean something different from what you expect so it must be more explicit than a cast (more you write, more you think about what you're doing).
This conversion can be done inside the object (using known IL instructions for that), using custom conversion operators (defined in the class to cast) or more complex mechanisms (TypeConverters or class methods, for example). You're not aware of what will happen to do that but you're aware it may fail (that's why IMO when a more controlled conversion is possible you should use it). In your case the conversion simply will parse the string to produce a double:
double value = Double.Parse(aStringVariable);
Of course this may fail so if you do it you should always catch the exception it may throw (FormatException). It's out of topic here but when a TryParse is available then you should use it (because semantically you say it may not be a number and it's even faster...to fail).
Conversions in .NET can come from a lot of places, TypeConverter, implicit/explicit casts with user defined conversion operators, implementation of IConvertible and parsing methods (did I forget something?). Take a look on MSDN for more details about them.
To finish this long answer just few words about user defined conversion operators. It's just sugar to let the programmer use a cast to convert one type to another. It's a method inside a class (the one that will be casted) that says "hey, if he/she wants to convert this type to that type then I can do it". For example:
float? maybe = 10; // Equals to Nullable<float> maybe = 10;
float sure1 = (float)maybe; // With cast
float sure2 = maybe.Value; // Without cast
In this case it's explicit because it may fail but this is let to the implementation (even if there are guidelines about this). Imagine you write a custom string class like this:
EasyString text = "123"; // Implicit from string
double value = (string)text; // Explicit to double
In your implementation you may decide to "make programmer's life easier" and to expose this conversion via a cast (remember it's just a shortcut to write less). Some language may even allow this:
double value = "123";
Allowing implicit conversion to any type (check will be done at run-time). With proper options this can be done, for example, in VB.NET. It's just a different philosophy.
What can I do with them?
So the final question is when you should use one or another. Let's see when you can use an explicit cast:
Conversions between base types.
Conversions from object to any other type (this may include unboxing too).
Conversions from a derived class to a base class (or to an implemented interface).
Conversions from one type to another via custom conversion operators.
Only the first conversion can be done with Convert so for the others you have no choice and you need to use an explicit cast.
Let's see now when you can use Convert:
Conversions from any base type to another base type (with some limitations, see MSDN).
Conversions from any type that implements IConvertible to any other (supported) type.
Conversions from/to a byte array to/from a string.
Conclusions
IMO Convert should be used each time you know a conversion may fail (because of the format, because of the range or because it may be unsupported), even if the same conversion can be done with a cast (unless something else is available). It makes clear to who will read your code what's your intent and that it may fail (simplifying debug).
For everything else you need to use a cast, no choice, but if another better method is available then I suggest you use it. In your example a conversion from string to double is something that (especially if text comes from user) very often will fail so you should make it as much explicit as possible (moreover you get more control over it), for example using a TryParse method.
Edit: what's the difference between them?
According to updated question and keeping what I wrote before (about when you can use a cast compared to when you can/have to use Convert) then last point to clarify is if there are difference between them (moreover Convert uses IConvertible and IFormattable interfaces so it can perform operations not allowed with casts).
Short answer is yes, they behave differently. I see the Convert class like a helper methods class so often it provides some benefit or slightly different behaviors. For example:
double real = 1.6;
int castedInteger = (int)real; // 1
int convertedInteger = Convert.ToInt32(real); // 2
Pretty different, right? The cast truncates (it's what we all expect) but Convert performs a rounding to nearest integer (and this may not be expected if you're not aware of it). Each conversion method introduces differences so a general rule can't be applied and they must be seen case by case...19 base types to convert to every other type...list can be pretty long, much better to consult MSDN case by case!
Casting is a way of telling the compiler, "I know that you think that this variable is a Bar, but I happen to know more than you; the object is actually a Foo, so let me treat it as if it were a Foo from now on." Then, at runtime, if the actual object turned out to really be a Foo then your code works, if it turns out that the object was not a Foo at all, then you get an exception. (Specifically an System.InvalidCastException.)
Converting on the other hand is a way of saying, "If you give me an object of type Bar I can create a brand new Foo object that represents what is in that Bar object. I won't change the original object, it won't treat the original object differently, it will create something new that is just based on some other value. As to how it will do that, it could be anything. In the case of Convert.ToDouble it will end up calling Double.Parse which has all sorts of complex logic for determining what types of strings represent what numeric values. You could write your own conversion method that mapped strings to doubles differently (perhaps to support some entirely different convention for displaying numbers, such as roman numerals or whatever). A conversion could do anything, but the idea is that you're not really asking the compiler to do anything for you; you are the one writing the code to determine how to create the new object because the compiler, without your help, has no way of knowing how to map (as an example) a string to a double.
So, when do you convert, and when do you cast? In both cases we have some variable of a type, let's say A, and we want to have a variable of type B. If our A object really, actually, under the hood, is a B, then we cast. If it's not really a B, then we need to Convert it, and define how the program is supposed to get a B from an A.
From MSDN:
Explicit conversions (casts): Explicit conversions require a cast operator. Casting is required when information might be lost in the conversion, or when the conversion might not succeed for other reasons. Typical examples include numeric conversion to a type that has less precision or a smaller range, and conversion of a base-class instance to a derived class.
Consider the following example:
double a = 2548.3;
int b;
b = (int)a; //2548 --> information (.3) lost in the conversion
And also:
A cast is a way of explicitly informing the compiler that you intend to make the conversion and that you are aware that data loss might occur.
You could use System.Convert class when you want to convert between non-compatible types. The main difference between casting and convert is compile and run-time. The type conversion exceptions are appeared at run-time , i.e a type cast that fails at run-time will cause an InvalidCastException to be thrown.
Conclusion: In casting you are telling to the compiler that a is really type b and if so the project builds without any errors like this example:
double s = 2;
int a = (int) s;
But in conversion you're saying to the compiler there is a way to create a new object from a of type b, please do it and project builds without any errors but as I said if type cast fails at run-time, it will cause an InvalidCastException to be thrown.
For example the code below is never compile because compiler detect that cannot cast expression of type DateTime to type int:
DateTime s = DateTime.Now;
int a = (int)(s);
But this one is compiled successfully:
DateTime s = DateTime.Now;
int a = Convert.ToInt32(s);
But at run-time you will get InvalidCastException which says:
Invalid cast from 'DateTime' to 'Int32'.
In your example you are attempting to cast a string to a double (non integral type).
An explicit conversion is required for it to work.
And i must point that you could have used Convert.ToDouble instead of Convert.ToInt64 as you can lose the fractional parts of the double value when you convert to an int.
if your variable has the value "5.25" varDouble would have been 5.00 (loss of 0.25 because of the Conversion to Int64)
To answer your question about casting vs converting.
Your cast (an explicit cast) doesn't meet the requirements for an explicit cast. the value you are trying to cast with the cast operator is invalid (i.e non integral).
Visit this MSDN Page for the rules of casting / conversions
The Convert.Double method actually just internally calls the Double.Parse(string) method.
Neither the String type nor the Double type define an explicit/implicit conversion between the two types, so casting will always fail.
The Double.Parse method will look at each character in the string and build a numeric value based on the values of the characters in the string. If any of the characters are invalid, the Parse method fails (causing the Convert.Double method to fail as well).
Casting does not involve any conversion, i.e. the internal representation of a value is not changed. Example:
object o = "Hello"; // o is typed as object and contains a string.
string s = (string)o; // This works only if o really contains a string or null.
You can convert a double to string like this
double d = 5;
string s = d.ToString(); // -> "5"
// Or by specifying a format
string formatted = d.ToString("N2"); // -> "5.00"
You can convert a string to a double in several ways (here just two of them):
string s = "5";
double d = Double.Parse(s); // Throws an exception if s does not contain a valid number
Or the safe way
string s = "5";
double d;
if (Double.TryParse(s, out d)) {
Console.WriteLine("OK. Result = {0}", d);
} else {
Console.WriteLine("oops!");
}
double varDouble = (double)variable assumes that variable is already a double. If variable isn't a double (it's a string) then this will fail.
double varDouble = Convert.ToDouble(variable) does like it says - it converts. If it can parse or otherwise extract a double from variable then it will.
I second using Double.Parse or Double.TryParse because it more clearly indicates what's supposed to be happening. You're starting with a string and expecting it to be convertible to a double. If there's any doubt, use TryParse.
If variable is a method argument, change the type to double. Make the caller responsible for providing the correct type. That way the compiler does the work for you.
string variable = "5.00";
double varDouble = (double)variable;
Above conversion is simply not allowed by language. Here is a list of explicit casts for numerical types: http://msdn.microsoft.com/en-us/library/yht2cx7b.aspx As you can see, even not every numerical type could be converted to another numerical type
Some more info about casting here
And how does this differ to Convert.ToDouble()?
When you cast a type, data structure is not changed. Well, in case of numerical values conversion it you may loose few bits or get few additional 0 bits. But you are still working with a number. You are just changing an amount of memory taken by that number. That is safe enough for compiler do everything needed.
But when you are trying to cast string to a number, you can't do that because it is not enough to change amount of memory taken by variable. For instance, 5.00 as a string is a sequence of "numbers":53(5) 46(.) 48(0) 48(0) - that is for ASCII, but string will contain something similar. If compiler will just take first N (4 for double? not sure) bytes from a string - that piece will contain completely different double number.
At the same time Convert.ToDouble() run special algorithm which will take each symbol of a string, figure out digit which it represents and make a double number for you, if string represents a number.
Languages like PHP will, roughly speaking, call Convert.ToDouble for you in background. But C#, like a statically typed language, will not do that for you. This allows you to be sure that any operation is type safe and you will not get something unexpected doing something like:
double d = (double)"zzzz"
Casting a string to a double like that is not allowed C# which is why you get an Exception, you need to have the string converted (MSDN doc that shows acceptable conversion paths). This is simply because a string isn't necessarily going to contain numeric data, but the various numeric types will (barring null values). A Convert will run a method that will check the string to see if it can be turned into a numeric value. If it can, then it will return that value. If it can't, it'll throw an exception.
To convert it, you have several options. You used the Convert method in your question, there's Parse which is largely similar to Convert, but you should also look at TryParse which would allow you to do:
string variable = "5.00";
double varDouble;
if (Double.TryParse(variable, out varDouble)) {
//Code that runs if the conversion succeeded.
} else {
//Code that runs if the conversion failed.
}
This avoids the possible exception should you try to Convert or Parse a non-numerical string.
The most important difference is that if type casting is used and the conversion fails
(say we are converting a very big float value to int ) no exception will be thrown and the minimum value an int can hold will be shown.
But in case of using Convert , an exception will be thrown for such scenarios.

Why can I pass 1 as a short, but not the int variable i?

Why does the first and second Write work but not the last? Is there a way I can allow all 3 of them and detect if it was 1, (int)1 or i passed in? And really why is one allowed but the last? The second being allowed but not the last really blows my mind.
Demo to show compile error
using System;
class Program
{
public static void Write(short v) { }
static void Main(string[] args)
{
Write(1);//ok
Write((int)1);//ok
int i=1;
Write(i);//error!?
}
}
The first two are constant expressions, the last one isn't.
The C# specification allows an implicit conversion from int to short for constants, but not for other expressions. This is a reasonable rule, since for constants the compiler can ensure that the value fits into the target type, but it can't for normal expressions.
This rule is in line with the guideline that implicit conversions should be lossless.
6.1.8 Implicit constant expression conversions
An implicit constant expression conversion permits the following conversions:
A constant-expression (§7.18) of type int can be converted to type sbyte, byte, short, ushort, uint, or ulong, provided the value of the constant-expression is within the range of the destination type.
A constant-expression of type long can be converted to type ulong, provided the value of the constant-expression is not negative.
(Quoted from C# Language Specification Version 3.0)
There is no implicit conversion from int to short because of the possibility of truncation. However, a constant expression can be treated as being of the target type by the compiler.
1? Not a problem: it’s clearly a valid short value. i? Not so much – it could be some value > short.MaxValue for instance, and the compiler cannot check that in the general case.
an int literal can be implicitly converted to short. Whereas:
You cannot implicitly convert nonliteral numeric types of larger storage size to short
So, the first two work because the implicit conversion of literals is allowed.
I believe it is because you are passing in a literal/constant in the first two, but there is not automatic type conversion when passing in an integer in the third.
Edit: Someone beat me to it!
The compiler has told you why the code fails:
cannot convert `int' expression to type `short'
So here's the question you should be asking: why does this conversion fail? I googled "c# convert int short" and ended up on the MS C# page for the short keyword:
http://msdn.microsoft.com/en-us/library/ybs77ex4(v=vs.71).aspx
As this page says, implicit casts from a bigger data type to short are only allowed for literals. The compiler can tell when a literal is out of range, but not otherwise, so it needs reassurance that you've avoided an out-of-range error in your program logic. That reassurance is provided by a cast.
Write((short)i)
Because there will not be any implicit conversion between Nonliteral type to larger sized short.
Implicit conversion is only possible for constant-expression.
public static void Write(short v) { }
Where as you are passing integer value as an argument to short
int i=1;
Write(i); //Which is Nonliteral here
Converting from int -> short might result in data truncation. Thats why.
Conversion from short --> int happens implicitly but int -> short will throw compile error bcoz it might result in data truncation.

Why can't I switch on a class with a single implicit conversion to an enum

I am wondering why it is that a single implicit conversion to an enum value doesn't work the same way it would if the conversion were to a system type. I can't see any technical reason however maybe someone smarter than I can shed some light for me.
The followoing fails to compile with, "A value of an integral type expected" and "Cannot implicitly convert type 'Test.En' to 'Test.Foo".
void test1 (){
Foo f = new Foo();
switch (f) // Comment this line to compile
//switch ((En)f) // Uncomment this line to compile
{
case En.One:
break;
}
}
//////////////////////////////////////////////////////////////////
public enum En
{
One,
Two,
Three,
}
public class Foo
{
En _myEn;
public static implicit operator En(Foo f)
{
return f._myEn;
}
}
edit from the spec:
The governing type of a switch statement is established by the switch expression. If the type of the switch expression is sbyte, byte, short, ushort, int, uint, long, ulong, char, string, or an enum-type, then that is the governing type of the switch statement. Otherwise, exactly one user-defined implicit conversion (§6.4) must exist from the type of the switch expression to one of the following possible governing types: sbyte, byte, short, ushort, int, uint, long, ulong, char, string. If no such implicit conversion exists, or if more than one such implicit conversion exists, a compile-time error occurs.
To Clarify the question, why is an enum-type not included with the list of allowed user-defined implicit conversions?
The language design notes archive does not provide a justification for this decision. This is unfortunate, since the decision was changed. As you can see, the design evolved over time:
Notes from May 26th, 1999:
What types are allowed in as the
argument to a switch statement?
integral types including char, enum
types, bool. C# also permits types
that can be implicitly and
unambiguously converted to one of the
aforementioned types. (If there are
multiple implicit conversion, then its
ambiguous and a compile-time error
occurs.) We're not sure whether we
want to support string or not.
June 7th, 1999:
We discussed enabling switch on string
arguments. We think this is a good
feature – the language can add value
by making this common case easier to
write, and the additional complexity
for the user is very low.
December 20th, 1999:
It is illegal to switch on an
expression of type bool. It is legal
to switch on an expression of an
integral type or string type. It is
legal to switch on an expression of a
type that has exactly one implicit
conversion to an integral type or
string type.
Here we have the first occurence of the rule in question. Enums seem to have disappeared. And why not use user-defined implicit conversions to enum? Was this simply an oversight? The designers did not record their thoughts.
Note that the first sentence is NOT what we implemented. It is unclear to me why the implementors did the opposite of what the design committee recommended. This comes up again in the notes several years later:
August 13, 2003:
The compiler allows switch on bool.
Don’t want to document this and add it
to the language. Don’t want to remove
it for compatibility reasons. Decided
to silently continue to support switch
on bool.
I decided that this was silly; when we produced the annotated print edition of the C# 3.0 specification, I added bool (and bool?) to the list of legal governing types.
In short: the whole thing is a bit of a mess. I have no idea why enums were in, then out, then half-in-half-out. This might have to remain one of the Mysteries of the Unknown.
Because enums are treated as integers for the purpose of switching, and as i've asked before, the compiler doesn't do multiple implicit conversions to get to a usable type, it can't figure out how to switch on foo.
My only theory as to why enums can't be used like that is that enums are not an integer type in and of themselves, and thus the compiler would have to do multiple implicit conversions to get to an integer primitive from foo.
I compiled then reflected your code and here's the results:
public static void Main()
{
Foo f = new Foo();
f._myEn = En.Three;
switch (f)
{
case En.One:
{
}
}
}
So apparently under the covers it does do an implicit conversion. :S
void test1 (){
Foo f = new Foo();
En n = f;
switch (n)
{
case En.One:
break;
}
}
EDIT: Since switch expects an integral value, writing switch(f) makes the compiler look for conversion from an instance of Foo to an integral type, which doesn't exist.
What if your class contained two enums and had implicit conversion operators for both? Or better yet, what if you had implicit conversion operators for an enum and int? Which conversion would the compiler "automatically" pick for you when you write a switch statement?
You have to explicitly specify what type of object is being used inside the switch statement. Implicit operators just tell the compiler/runtime "if you have a Foo and need an En, this code does that". It does not change the actual underlying type of the object.
Take a look at that second error message. The compiler is trying to type-coerce the enum to match the type of what is in the switch statement.
As a point of interest, how does this fare?
void test2 (){
Foo f = new Foo();
switch (En.One)
{
case f:
break;
}
}

Categories

Resources