First of all: I know how to work around this issue. I'm not searching for a solution. I am interested in the reasoning behind the design choices that led to some implicit conversions and didn't lead to others.
Today I came across a small but influential error in our code base, where an int
constant was initialised with a char
representation of that same number. This results in an ASCII
conversion of the char
to an int
. Something like this:
char a = 'a';
int z = a;
Console.WriteLine(z);
// Result: 97
I was confused why C# would allow something like this. After searching around I found the following SO question with an answer by Eric Lippert himself: Implicit Type cast in C#
An excerpt:
However, we can make educated guesses as to why implicit char-to-ushort was considered a good idea. The key idea here is that the conversion from number to character is a "possibly dodgy" conversion. It's taking something that you do not KNOW is intended to be a character, and choosing to treat it as one. That seems like the sort of thing you want to call out that you are doing explicitly, rather than accidentally allowing it. But the reverse is much less dodgy. There is a long tradition in C programming of treating characters as integers -- to obtain their underlying values, or to do mathematics on them.
I can agree with the reasoning behind it, though an IDE hint would be awesome. However, I have another situation where the implicit conversion suddenly is not legal:
char a = 'a';
string z = a; // CS0029 Cannot implicitly convert type 'char' to 'string'
This conversion is in my humble opinion, very logical. It cannot lead to data loss and the intention of the writer is also very clear. Also after I read the rest of the answer on the char
to int
implicit conversion, I still don't see any reason why this should not be legal.
So that leads me to my actual question:
What reasons could the C# design team have, to not implement the implicit conversion from char
to a string
, while it appears so obvious by the looks of it (especially when comparing it to the char
to int
conversion).
We can convert a char to a string object in java by using the Character. toString() method.
In C#, there are two types of casting: Implicit Casting (automatically) - converting a smaller type to a larger type size. char -> int -> long -> float -> double. Explicit Casting (manually) - converting a larger type to a smaller size type.
You can implicitly convert from an expression of type dynamic to any other type. What this means is that the compiler allows an implicit conversion (no cast operator) from the dynamic type to any other type and compilation succeeds.
First off, as I always say when someone asks "why not?" question about C#: the design team doesn't have to provide a reason to not do a feature. Features cost time, effort and money, and every feature you do takes time, effort and money away from better features.
But I don't want to just reject the premise out of hand; the question might be better phrased as "what are design pros and cons of this proposed feature?"
It's an entirely reasonable feature, and there are languages which allow you to treat single characters as strings. (Tim mentioned VB in a comment, and Python also treats chars and one-character strings as interchangeable IIRC. I'm sure there are others.) However, were I pitched the feature, I'd point out a few downsides:
The feature will not be perceived as "chars are convertible to one-character strings". It will be perceived by users as "chars are one-character strings", and now it is perfectly reasonable to ask lots of knock-on questions, like: can call .Length
on a char? If I can pass a char to a method that expects a string
, and I can pass a string to a method that expects an IEnumerable<char>
, can I pass a char
to a method that expects an IEnumerable<char>
? That seems... odd. I can call Select
and Where
on a string; can I on a char? That seems even more odd. All the proposed feature does is move your question; had it been implemented, you'd now be asking "why can't I call Select on a char?" or some such thing.
Now combine the previous two points together. If I think of chars as one-character strings, and I convert a char to an object, do I get a boxed char or a string?
List<char>
? Why stop with char
? Should we say that an int
is convertible to IEnumerable<int>
? Task<char>
-- just create a completed task that returns the char -- and to Func<char>
-- just create a lambda that returns the char -- and to Lazy<char>
, and to Nullable<char>
-- oh, wait, we do allow a conversion to Nullable<char>
. :-)All of these problems are solvable, and some languages have solved them. That's not the issue. The issue is: all of these problems are problems that the language design team must identify, discuss and resolve. One of the fundamental problems in language design is how general should this feature be? In two minutes I've gone from "chars are convertible to single-character strings" to "any value of an underlying type is convertible to an equivalent value of a monadic type". There is an argument to be made for both features, and for various other points on the spectrum of generality. If you make your language features too specific, it becomes a mass of special cases that interact poorly with each other. If you make them too general, well, I guess you have Haskell. :-)
Suppose the design team comes to a conclusion about the feature: all of that has to be written up in the design documents and the specification, and the code, and tests have to be written, and, oh, did I mention that any time you make a change to convertibility rules, someone's overload resolution code breaks? Convertibility rules you really have to get right in the first version, because changing them later makes existing code more fragile. There are real design costs, and there are real costs to real users if you make this sort of change in version 8 instead of version 1.
Now compare these downsides -- and I'm sure there are more that I haven't listed -- to the upsides. The upsides are pretty tiny: you avoid a single call to ToString
or + ""
or whatever you do to convert a char to a string explicitly.
That's not even close to a good enough benefit to justify the design, implementation, testing, and backwards-compat-breaking costs.
Like I said, it's a reasonable feature, and had it been in version 1 of the language -- which did not have generics, or an installed base of billions of lines of code -- then it would have been a much easier sell. But now, there are a lot of features that have bigger bang for smaller buck.
Char
is a value type. No, it is not numerical like int
or double, but it still derives from System.ValueType
. Then a char
variable lives on the stack.
String
is a reference type. So it lives on the heap. If you want to use a value type as a reference type, you need to box it first. The compiler needs to be sure that you know what you're doing via boxing. So, you can't have an implicit cast.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With