Overview of type suffixes in C# and VB.Net

Overview of type suffixes in C# and VB.Net

I never seem to remember that “D” does not stand for Decimal in C#, but does mean Decimal in VB.Net. And my occasional search — in vain, obviously — for type suffixes for byte, signed byte, char (as decimal), short or unsigned short made me write this article for once and for all. Type suffixes are a pain, they lie and cheat, keep this overview as a reference.

Type suffixes

In day-to-day code it is not all too common to find type suffixes. Most programmers I work with or tutor, tend to use type suffixes the moment they find out that the outcome is not as expected. Then they experiment a bit until they find the right type suffix, not always understanding why it is actually needed.

float averageLifeExpectancy = 2.538;           // error
float abgAntsLife = 2.538F;          // OK
long veryMuch = 1234567890123456789  // error
long veryMuch = 1234567890123456789L // OK

Other uses are when you need literals for an overloaded method. In the following example, what method do your think will be called?

// from 1-1-1972
DateTime fromEpoch(uint seconds)
{
    return DateTime.FromFileTime(seconds)
}
// from 1AD
DateTime fromEpoch(ulong nanosec)
{
    return new DateTime((ulong) (nanosec / 100));
}
DateTime date
date = fromEpoch(2,439,234,332);   // which is called (commas for clarity)?

I’m sure you know the answer, so I won’t gonna bother explaining. However, remember the fromEpoch, we will need them later when I show you how type suffixes can lie and cause faulty code. For now, let’s cut to the chase and show the little overview:

suffix
C#
suffix
VB
VB type
identifier
primary secondary C# example VB example
byte (byte) 130 130 As Byte
sbyte (sbyte) 120 120 As SByte
S, s short (short) 57721 57721S 1
US, us ushort (ushort) 123456 123456US 2
I, i % int uint, long, ulong 2078795763 2078795763I 3
U, u UI, ui uint long, ulong 314159265U 314159265UI 4
L, l* L, l & long ulong 271828…235L 271828…235L 5
UL,ul,
LU, lu
UL, ul ulong 12345…141LU 12345…141UL 2
F, f F, f ! float ** 0.1101F 0.1101F 6
D, d R, r # double ** 0.11010011001D 0.11010011001R 6
M, m D, d @ decimal ** 0.1101001100101101M 0.1101001100101101D 6
C, c char '₰' or 'Y' or '\'' "₰"c or "Y"c or "'"c
$ string "ᴚᴐᴎᴂ" or "\"" or @"""" "ᴚᴐᴎᴂ" or """"

* small letter l will raise a warning, for it is easy confused with number 1
** float, double and decimal are not converted into other types automatically

Missing C# type suffixes for Byte, Short and Char

It is beyond me why the C# Team didn’t create type suffixes for all the standard value types. Doing a byte calculation requires excessive casting and using digits for a char is not possible either. It is probably too late in the game now, but filling in those blanks surely couldn’t have been too hard for the compiler, nor would it add too much bloat to the spec. Instead, it would’ve given the language a more consistent feel.

To: Anders Hejlsberg and Mads Torgersen, can you add this feature in an upcoming release, just for the purpose of clearer code and better consistency throughout?

Secrets of constants and type suffixes

The design of the type system is such that in most situations you need not worry about the implications. Implicit casts are your friend and make sure that your use of the basic types automagically works as expected. However, when using calculations that involve mixing different basic value types, like integers and longs, you need to be careful. The default value type is int for any literal and during the calculation, this default can break the result of the calculation.

Suppose your teachers asks you to come up with a program to calculate the cubic millimeters (mm3) of the Olympic Swimming Pool and you come up with this example:

long FromLiterToCubicMm(int liters)
{
    return liters * 1000000;
}

Perhaps not very realistic, but it shows what can happen:  it will often return an incorrect value! Let’s try to use as input the amount of liters in an Olympic Swimming Pool:

Console.WriteLine(
    String.Format("Olympic Swimming Pool contains {0} cubic mm",
    FromLiterToCubicMm(2500000));

And the output will print:

Olympic Swimming Pool contains 329033728 cubic mm

Which is not what you would expect. The reason is simple yet confusing: the liters variable is of type int and the default of the literal 1000000  is also int. This brings the default type during the calculation to also be an int, which overflows as soon as it multiplies a too high number. After the calculation, the int value is implicitly cast to a long.

One might expect, because of the return type, that the operands of the calculation are implicitly cast to the same type as the return type. Alas, that’s not the case here.

Fixing this type of error is easy: choose a large enough type that can contain your data and use that everywhere. If you need to use an int in a calculation that can return a long, make sure that at least one the operands contains the target type. In our case, simply adding the type suffix would yield the correct result:

long FromLiterToCubicMm(int liters)
{
    return liters * 1000000L;
}

In day-to-day programming, errors like the above are rare. But if they happen, they can be very hard to track down. The default of the C# language is to do calculation in an unchecked state. This makes it easier to do JIT optimizations. In contrast, during compiling, the default is checked: if you use constants and literals and the calculation of these creates an overflow, you’ll receive a compile error.

Lies introduced with or without type suffixes

Type suffixes can be dangerous. They never force an overflow. If you define a constant, and you add a type suffix to force the constant to be in a certain range, the type will automatically upgrade to the first larger type that fits the literal. Consider the following code example, which prints the names of the underlying types. Can you predict the outcome?

Console.WriteLine(4400200300.GetType().Name);
Console.WriteLine(4400200300U.GetType().Name);
Console.WriteLine(4400200300L.GetType().Name);
Console.WriteLine(4400200300UL.GetType().Name);

You’re a smart guy if you really have guessed the outcome of this little exercise. Naturally you noticed that the number, 4400200300 does not fit an int or a uint, but what is the type it becomes, if it doesn’t throw an error or a warning? This is the output:

Visual Studio editor shows UInt32 as UInt64

Visual Studio editor shows UInt32 as UInt64

Int64
UInt64
Int64
UInt64

The tooltips of Visual Studio are smart enough to spot this little idiosynchrasy as well, see the screenshot.

The Rule of thumb for integers

The rule of thumb to memorize is the following, which will save your day someday:

Take the first available bit size that can hold the literal value and leave the sign bit, if any, intact.

The Rule of thumb for floating point numbers

Totally opposite from what you might expect after you read that story of the integers, is the behavior in floating point county. What the compiler should have done in the case of integers, it does in the case of floating points. The rule is simple:

If the literal does not fit the designated type, a compile time error occurs

In other words, the following will not compile, each line will throw an error:

Overflow of floating points in Visual Studio

Overflow of floating points in Visual Studio

var largeFloat    = 1e40F;
var largeDouble   = 1e500D;
var largeDecimal  = 1e30M;

And it doesn’t help even a bit if you put this code in an unchecked block. Would that prevent compile time overflow errors for integer types, it won’t help a thing for floating and fixed point types.

Table of automatic conversions

TBD

References

This post could not be made without the ongoing efforts of the wikipedians and other writers who were so kind to inspire me about using proper constants that may actually figurate for real constants in my programs one day. All numbers are transcendental and a list of them can be found here: http://sprott.physics.wisc.edu/Pickover/trans.html.

Visual Basic .NET type characters, suffixes and identifiers: http://msdn.microsoft.com/en-us/library/s9cz43ek.aspx
Byte does not have a type character of type suffix: http://msdn.microsoft.com/en-us/library/e2ayt412.aspx
Section 2.4.4 of the C# Language Specification: http://msdn.microsoft.com/en-us/library/aa664674(VS.71).aspx
A fine example of why L is preferred of l and causes a compiler warning: http://dotnetperls.com/suffix-examples

– Abel –

  • http://www.rogerdickeyjr.com/ Roger

    Wow, thanks! I was banging my head against the keyboard for a while all because I was unaware of these type suffixes.

Get Adobe Flash player