“Char” oddity (in .Net Framework)

David Bowie (1969 album)
On the wave of the unforgettable song of David Bowie, here is a short post about the “oddities” around the Char type, in the .Net Framework.

The problem.

Today I bumped against a strange effect.
At first glance I thought to a bug or something like that, since I saw this effect on the Micro Framework. By the way, the little nephew of the ordinary desktop is working fine, because the same problem happens on any .Net application. Thinking better on the “problem”, well…yeah, it’s not a problem!
Yes, it’s not a bug, but can lead easily to subtle side-effects, mistakes, etc. I hate them, because they lead to a long waste of time, and I don’t have, usually.
So, what?
Open your favorite Visual Studio IDE, then create a simple console application. Afterward, copy-and-paste the following code:

    class Program
    {

        static void Main(string[] args)
        {
            Print(
                'H' + "ello!"
                );

            Console.WriteLine();
            Console.Write("Press any key...");
            Console.ReadKey();
        }


        static void Print(string s)
        {
            Console.WriteLine(s);
        }

    }

What do you expect to see in the output window as the “Print” function does?
No doubt:

Hello!

Now, add a piece more to the code.

    class Program
    {

        static void Main(string[] args)
        {
            Print(
                'H' + "ello!"
                );

            Print(
                'H' + 'H' + "ello!"
                );

            Console.WriteLine();
            Console.Write("Press any key...");
            Console.ReadKey();
        }


        static void Print(string s)
        {
            Console.WriteLine(s);
        }

    }

At this point what should be the resulting output?

image1
Hmm…that’s really strange!

Let’s add another piece.

    class Program
    {

        static void Main(string[] args)
        {
            Print(
                'H' + "ello!"
                );

            Print(
                'H' + 'H' + "ello!"
                );

            Print(
                'H' + ('H' + "ello!")
                );

            Console.WriteLine();
            Console.Write("Press any key...");
            Console.ReadKey();
        }


        static void Print(string s)
        {
            Console.WriteLine(s);
        }

    }

The result begins to give us an hint.

image2
Let’s go further…

    class Program
    {

        static void Main(string[] args)
        {
            Console.WriteLine("Leading...");
            Print(
                'H' + "ello!"
                );

            Print(
                'H' + 'H' + "ello!"
                );

            Print(
                'H' + ('H' + "ello!")
                );


            Console.WriteLine();
            Console.WriteLine("Trailing...");
            Print(
                "Hello" + '!'
                );

            Print(
                "Hello" + '!' + '!'
                );

            Console.WriteLine();
            Console.Write("Press any key...");
            Console.ReadKey();
        }


        static void Print(string s)
        {
            Console.WriteLine(s);
        }

    }

Aw!…now the “trailing” section seems working fine even without parenthesis…

image3

The reason behind the “oddity”.

The above behavior is due to the implicit cast conversion of a Char to an Int32: this cast conversion is performed even at compile time whenever possible.
Let’s check the IL code of the beginning of the program.

        ...
	IL_000c: ldc.i4.s 72
	IL_000e: box [mscorlib]System.Char
	IL_0013: ldstr "ello!"
	IL_0018: call string [mscorlib]System.String::Concat(object, object)
	IL_001d: call void ConsoleApplication1.Program::Print(string)

	IL_0022: nop
	IL_0023: ldc.i4 144                  //<--the compiler calculated the "sum" of the chars
	IL_0028: box [mscorlib]System.Int32  //and recognize it as an Int32
	IL_002d: ldstr "ello!"
	IL_0032: call string [mscorlib]System.String::Concat(object, object)
	IL_0037: call void ConsoleApplication1.Program::Print(string)

	IL_003c: nop
	IL_003d: ldc.i4.s 72
	IL_003f: box [mscorlib]System.Char
	IL_0044: ldc.i4.s 72
	IL_0046: box [mscorlib]System.Char
	IL_004b: ldstr "ello!"
	IL_0050: call string [mscorlib]System.String::Concat(object, object, object)
	IL_0055: call void ConsoleApplication1.Program::Print(string)
	...

This designers’ choice to cast convert implicitly also leads to some curious issues:

    class Program
    {

        static void Main(string[] args)
        {
            //you may "sum" to chars together
            Console.WriteLine();
            int x = 'A' + 'B';
            Console.WriteLine(x);   //yields 131


            //explicit cast to int 
            Console.WriteLine();
            object o = 'Q';
            int q = (int)o; //invalid cast exception


            //sum over a string using Linq
            var t = "Hello world!";

            int u1 = t.Sum(_ => _);
            Console.WriteLine(u1);  //yields 1117

            int u2 = t.Cast<int>().Sum(_ => _);  //invalid cast exception

            Console.WriteLine();
            Console.Write("Press any key...");
            Console.ReadKey();
        }


        static void Print(string s)
        {
            Console.WriteLine(s);
        }

    }

UPDATE: I posted the question on StackOverflow, but -at the moment- none gave a decent answer. Even the fully respectable guess of Eric Lippert leave me unsatisfied, believing much more a “kind of gift” for the C/C++ users, other than an useful rule for a safer programming.
In short, I still believe that was a bad decision.

Be careful!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s