Quantcast
Channel: Java – The Wiert Corner – irregular stream of stuff
Viewing all articles
Browse latest Browse all 65

.NET/C# duh moment of the day: “A char can be implicitly converted to ushort, int, uint, long, ulong, float, double, or decimal (not the other way around; implicit != implicit)”

$
0
0

A while ago I had a “duh” moment while calling a method that had many overloads, and one of the overloads was using int, not the char I’d expect.

The result was that a default value for that char was used, and my parameter was interpreted as a (very small) buffer size. I only found out something went wrong when writing unit tests around my code.

The culprit is this C# char feature (other implicit type conversions nicely summarized by Muhammad Javed):

A char can be implicitly converted to ushort, int, uint, long, ulong, float, double, or decimal. However, there are no implicit conversions from other types to the char type.

Switching between various development environments, I totally forgot this is the case in languages based on C and Java ancestry. But not in VB and Delphi ancestry  (C/C++ do numeric promotions of char to int and Java widens 2-byte char to 4-byte int; Delphi and VB.net don’t).

I’m not the only one who was confused, so Eric Lippert wrote a nice blog post on it in 2009: Why does char convert implicitly to ushort but not vice versa? – Fabulous Adventures In Coding – Site Home – MSDN Blogs.

Basically, it is the C ancestry: a char is an integral type always known to contain an integer value representing a Unicode character. The opposite is not true: an integer type is not always representing a Unicode character.

Lesson learned: if you have a large number of overloads (either writing them or using them) watch for mixing char and int parameters.

Note that overload resolution can be diffucult enough (C# 3 had breaking changes and C# 4 had breaking changes too, and those are only for C#), so don’t make it more difficult than it should be (:

Below a few examples in C# and VB and their IL disassemblies to illustrate their differnces based on asterisk (*) and space ( ) that also show that not all implicits are created equal: Decimal is done at run-time, the rest at compile time.

Note that the order of the methods is alphabetic, but the calls are in order of the type and size of the numeric types (integral types, then floating point types, then decimal).

A few interesting observations:

  • The C# compiler implicitly converts char with all calls except for decimal, where an implicit conversion at run time is used:
    L_004c: call valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Implicit(char)
    L_0051: call void CharIntCompatibilityCSharp.Program::writeLineDecimal(valuetype [mscorlib]System.Decimal)
  • Same for implicit conversion of byte to the other types, though here the C# and VB.NET compilers generate slightly different code for run-time conversion.
    C# uses an implicit conversion:
    L_00af: ldloc.1
    L_00b0: call valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Implicit(uint8)
    L_00b5: call void CharIntCompatibilityCSharp.Program::writeLineDecimal(valuetype [mscorlib]System.Decimal)
    VB.NET calls a constructor:
    L_006e: ldloc.1
    L_006f: newobj instance void [mscorlib]System.Decimal::.ctor(int32)
    L_0075: call void CharIntCompatibilityVB.Program::writeLineDecimal(valuetype [mscorlib]System.Decimal)

Here is the example code:

C# code:

using System;

namespace CharIntCompatibilityCSharp
{
    class Program
    {
        static void Main()
        {
            Console.WriteLine("asterisk");
            char asterisk = '*';
            //writeLineSbyte(asterisk); // no implicit conversion
            //writeLineByte(asterisk); // no implicit conversion
            //writeLineShort(asterisk); // no implicit conversion
            writeLineChar(asterisk);
            writeLineUshort(asterisk); // implicit conversion
            writeLineInt(asterisk);
            writeLineUint(asterisk);
            writeLineLong(asterisk);
            writeLineUlong(asterisk);
            writeLineFloat(asterisk);
            writeLineDouble(asterisk);
            writeLineDecimal(asterisk);
            Console.WriteLine("space");
            byte space = 32;
            writeLineSbyte(space);
            writeLineByte(space);
            writeLineShort(space);
            //writeLineChar(space); // no implicit conversion
            writeLineUshort(space);
            writeLineInt(space);
            writeLineUint(space);
            writeLineLong(space);
            writeLineUlong(space);
            writeLineFloat(space);
            writeLineDouble(space);
            writeLineDecimal(space);
            Console.Write("Press <Enter>");
            Console.ReadLine();
        }

        private static void writeLineByte(byte character)
        {
            Console.WriteLine((int)character);
        }

        private static void writeLineChar(char character)
        {
            Console.WriteLine(character);
        }

        private static void writeLineDecimal(decimal character)
        {
            Console.WriteLine(character);
        }

        private static void writeLineDouble(double character)
        {
            Console.WriteLine(character);
        }

        private static void writeLineFloat(float character)
        {
            Console.WriteLine(character);
        }

        private static void writeLineInt(int character)
        {
            Console.WriteLine(character);
        }

        private static void writeLineLong(long character)
        {
            Console.WriteLine(character);
        }

        private static void writeLineSbyte(byte character)
        {
            Console.WriteLine((int)character);
        }

        private static void writeLineShort(short character)
        {
            Console.WriteLine((int)character);
        }

        private static void writeLineUint(uint character)
        {
            Console.WriteLine(character);
        }

        private static void writeLineUlong(ulong character)
        {
            Console.WriteLine(character);
        }

        private static void writeLineUshort(ushort character)
        {
            Console.WriteLine((int)character);
        }
    }
}

VB.NET code:

Friend Class Program
    ' Methods
    Friend Shared Sub Main()
        Console.WriteLine("asterisk")
        Dim asterisk As Char = "*"c
        'writeLineSbyte(asterisk) 'no implicit conversion
        'writeLineByte(asterisk) 'no implicit conversion
        'writeLineShort(asterisk) 'no implicit conversion
        writeLineChar(asterisk)
        'writeLineUshort(asterisk) 'no implicit conversion
        'writeLineInt(asterisk) 'no implicit conversion
        'writeLineUint(asterisk) 'no implicit conversion
        'writeLineLong(asterisk) 'no implicit conversion
        'writeLineUlong(asterisk) 'no implicit conversion
        'writeLineFloat(asterisk) 'no implicit conversion
        'writeLineDouble(asterisk) 'no implicit conversion
        'writeLineDecimal(asterisk) 'no implicit conversion
        Console.WriteLine("space")
        Dim space As Byte = 32
        writeLineSbyte(space)
        writeLineByte(space)
        writeLineShort(space)
        'writeLineChar(space) 'no implicit conversion
        writeLineUshort(space)
        writeLineInt(space)
        writeLineUint(space)
        writeLineLong(space)
        writeLineUlong(space)
        writeLineFloat(space)
        writeLineDouble(space)
        writeLineDecimal(space)
        Console.Write("Press <Enter>")
        Console.ReadLine()
    End Sub

    Private Shared Sub writeLineByte(ByVal character As Byte)
        Console.WriteLine(character)
    End Sub

    Private Shared Sub writeLineChar(ByVal character As Char)
        Console.WriteLine(character)
    End Sub

    Private Shared Sub writeLineDecimal(ByVal character As Decimal)
        Console.WriteLine(character)
    End Sub

    Private Shared Sub writeLineDouble(ByVal character As Double)
        Console.WriteLine(character)
    End Sub

    Private Shared Sub writeLineFloat(ByVal character As Single)
        Console.WriteLine(character)
    End Sub

    Private Shared Sub writeLineInt(ByVal character As Integer)
        Console.WriteLine(character)
    End Sub

    Private Shared Sub writeLineLong(ByVal character As Long)
        Console.WriteLine(character)
    End Sub

    Private Shared Sub writeLineSbyte(ByVal character As Byte)
        Console.WriteLine(character)
    End Sub

    Private Shared Sub writeLineShort(ByVal character As Short)
        Console.WriteLine(character)
    End Sub

    Private Shared Sub writeLineUint(ByVal character As UInt32)
        Console.WriteLine(character)
    End Sub

    Private Shared Sub writeLineUlong(ByVal character As UInt64)
        Console.WriteLine(character)
    End Sub

    Private Shared Sub writeLineUshort(ByVal character As UInt16)
        Console.WriteLine(character)
    End Sub

End Class

C# IL of Main()

.method private hidebysig static void Main() cil managed
{
    .entrypoint
    .maxstack 1
    .locals init (
        [0] char asterisk,
        [1] uint8 space)
    L_0000: nop
    L_0001: ldstr "asterisk"
    L_0006: call void [mscorlib]System.Console::WriteLine(string)
    L_000b: nop
    L_000c: ldc.i4.s 0x2a
    L_000e: stloc.0
    L_000f: ldloc.0
    L_0010: call void CharIntCompatibilityCSharp.Program::writeLineChar(char)
    L_0015: nop
    L_0016: ldloc.0
    L_0017: call void CharIntCompatibilityCSharp.Program::writeLineUshort(uint16)
    L_001c: nop
    L_001d: ldloc.0
    L_001e: call void CharIntCompatibilityCSharp.Program::writeLineInt(int32)
    L_0023: nop
    L_0024: ldloc.0
    L_0025: call void CharIntCompatibilityCSharp.Program::writeLineUint(uint32)
    L_002a: nop
    L_002b: ldloc.0
    L_002c: conv.u8
    L_002d: call void CharIntCompatibilityCSharp.Program::writeLineLong(int64)
    L_0032: nop
    L_0033: ldloc.0
    L_0034: conv.u8
    L_0035: call void CharIntCompatibilityCSharp.Program::writeLineUlong(uint64)
    L_003a: nop
    L_003b: ldloc.0
    L_003c: conv.r4
    L_003d: call void CharIntCompatibilityCSharp.Program::writeLineFloat(float32)
    L_0042: nop
    L_0043: ldloc.0
    L_0044: conv.r8
    L_0045: call void CharIntCompatibilityCSharp.Program::writeLineDouble(float64)
    L_004a: nop
    L_004b: ldloc.0
    L_004c: call valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Implicit(char)
    L_0051: call void CharIntCompatibilityCSharp.Program::writeLineDecimal(valuetype [mscorlib]System.Decimal)
    L_0056: nop
    L_0057: ldstr "space"
    L_005c: call void [mscorlib]System.Console::WriteLine(string)
    L_0061: nop
    L_0062: ldc.i4.s 0x20
    L_0064: stloc.1
    L_0065: ldloc.1
    L_0066: call void CharIntCompatibilityCSharp.Program::writeLineSbyte(uint8)
    L_006b: nop
    L_006c: ldloc.1
    L_006d: call void CharIntCompatibilityCSharp.Program::writeLineByte(uint8)
    L_0072: nop
    L_0073: ldloc.1
    L_0074: call void CharIntCompatibilityCSharp.Program::writeLineShort(int16)
    L_0079: nop
    L_007a: ldloc.1
    L_007b: call void CharIntCompatibilityCSharp.Program::writeLineUshort(uint16)
    L_0080: nop
    L_0081: ldloc.1
    L_0082: call void CharIntCompatibilityCSharp.Program::writeLineInt(int32)
    L_0087: nop
    L_0088: ldloc.1
    L_0089: call void CharIntCompatibilityCSharp.Program::writeLineUint(uint32)
    L_008e: nop
    L_008f: ldloc.1
    L_0090: conv.u8
    L_0091: call void CharIntCompatibilityCSharp.Program::writeLineLong(int64)
    L_0096: nop
    L_0097: ldloc.1
    L_0098: conv.u8
    L_0099: call void CharIntCompatibilityCSharp.Program::writeLineUlong(uint64)
    L_009e: nop
    L_009f: ldloc.1
    L_00a0: conv.r4
    L_00a1: call void CharIntCompatibilityCSharp.Program::writeLineFloat(float32)
    L_00a6: nop
    L_00a7: ldloc.1
    L_00a8: conv.r8
    L_00a9: call void CharIntCompatibilityCSharp.Program::writeLineDouble(float64)
    L_00ae: nop
    L_00af: ldloc.1
    L_00b0: call valuetype [mscorlib]System.Decimal [mscorlib]System.Decimal::op_Implicit(uint8)
    L_00b5: call void CharIntCompatibilityCSharp.Program::writeLineDecimal(valuetype [mscorlib]System.Decimal)
    L_00ba: nop
    L_00bb: ldstr "Press <Enter>"
    L_00c0: call void [mscorlib]System.Console::Write(string)
    L_00c5: nop
    L_00c6: call string [mscorlib]System.Console::ReadLine()
    L_00cb: pop
    L_00cc: ret
}

VB.NET IL of Main():

.method assembly static void Main() cil managed
{
    .custom instance void [mscorlib]System.STAThreadAttribute::.ctor()
    .entrypoint
    .maxstack 2
    .locals init (
        [0] char asterisk,
        [1] uint8 space)
    L_0000: nop
    L_0001: ldstr "asterisk"
    L_0006: call void [mscorlib]System.Console::WriteLine(string)
    L_000b: nop
    L_000c: ldc.i4.s 0x2a
    L_000e: stloc.0
    L_000f: ldloc.0
    L_0010: call void CharIntCompatibilityVB.Program::writeLineChar(char)
    L_0015: nop
    L_0016: ldstr "space"
    L_001b: call void [mscorlib]System.Console::WriteLine(string)
    L_0020: nop
    L_0021: ldc.i4.s 0x20
    L_0023: stloc.1
    L_0024: ldloc.1
    L_0025: call void CharIntCompatibilityVB.Program::writeLineSbyte(uint8)
    L_002a: nop
    L_002b: ldloc.1
    L_002c: call void CharIntCompatibilityVB.Program::writeLineByte(uint8)
    L_0031: nop
    L_0032: ldloc.1
    L_0033: call void CharIntCompatibilityVB.Program::writeLineShort(int16)
    L_0038: nop
    L_0039: ldloc.1
    L_003a: call void CharIntCompatibilityVB.Program::writeLineUshort(uint16)
    L_003f: nop
    L_0040: ldloc.1
    L_0041: call void CharIntCompatibilityVB.Program::writeLineInt(int32)
    L_0046: nop
    L_0047: ldloc.1
    L_0048: call void CharIntCompatibilityVB.Program::writeLineUint(uint32)
    L_004d: nop
    L_004e: ldloc.1
    L_004f: conv.u8
    L_0050: call void CharIntCompatibilityVB.Program::writeLineLong(int64)
    L_0055: nop
    L_0056: ldloc.1
    L_0057: conv.u8
    L_0058: call void CharIntCompatibilityVB.Program::writeLineUlong(uint64)
    L_005d: nop
    L_005e: ldloc.1
    L_005f: conv.r4
    L_0060: call void CharIntCompatibilityVB.Program::writeLineFloat(float32)
    L_0065: nop
    L_0066: ldloc.1
    L_0067: conv.r8
    L_0068: call void CharIntCompatibilityVB.Program::writeLineDouble(float64)
    L_006d: nop
    L_006e: ldloc.1
    L_006f: newobj instance void [mscorlib]System.Decimal::.ctor(int32)
    L_0074: nop
    L_0075: call void CharIntCompatibilityVB.Program::writeLineDecimal(valuetype [mscorlib]System.Decimal)
    L_007a: nop
    L_007b: ldstr "Press <Enter>"
    L_0080: call void [mscorlib]System.Console::Write(string)
    L_0085: nop
    L_0086: call string [mscorlib]System.Console::ReadLine()
    L_008b: pop
    L_008c: nop
    L_008d: ret
}

–jeroen

via:


Filed under: .NET, Agile, C#, C# 1.0, C# 2.0, C# 3.0, C# 4.0, C# 5.0, C++, Delphi, Development, Encoding, Java, Software Development, Unicode, Unit Testing, VB.NET

Viewing all articles
Browse latest Browse all 65

Trending Articles