If you’re not new to .NET, you’ve probably heard several times that one type can be a value type or a reference type. Well, that is true. What people miss (sometimes) is that they’ve also got some data types which are used so often that compilers allow code to treat them in a simplified way: say hello to the primitive types.
Lets look at a small example. Nothing prevents us from writing this code:
System.Int32 myInt = new System.Int32();
myInt = 10;
But does anyone want to write that when they simply can write this:
System.Int32 myInt = 10;
Oh, yes, you can reduce it even further by using the int alias:
int myInt = 10;
Any data type that is *directly* supported by the compiler can be considered a primitive type. Notice the *directly*…it’s used here to indicate that the compiler knows what to do in order to create a new instance from “simplified” code.
Btw, you’ve surely noticed the int alias, haven’t you? In fact, it’s not really an alias but a C# keyword that is mapped into the System.Int32 FCL type. Even though you can’t introduce new keywords, you can still introduce new alias to simplify the use of certain types. Here’s an example:
using integer = System.Int32;
And now it’s possible to use integer as an alias to the System.Int32 type (btw, I don’t recommend doing this). Currently, there are several types which are treated as primitive by the C#. In that list, you’ll find plenty of numeric types (Int32, Double, etc), strings (String or string – if you prefer the C# keyword), the object (System.Object) type and even the new dynamic type (which, btw, is mapped into an Object instance). As you can see, primitive types aren’t really limited to a subset of value types.
Besides knowing how to create these types, the compiler is also able to perform other interesting operations over them. For instance, it is able to convert automatically between two types without having any relationship between them. Here’s an example:
Int32 myInt = 10;
Int64 myLong = myInt;
Since Int64 and Int32 aren’t “related”, you can only get away with the previous code because you’re using primitive types (and the compiler has privileged knowledge about them!). The C# compiler will allow this type of implicit conversion only when it knows it’s safe (ie, when it knows that there are no data loss during the conversion – which doesn’t happen ion the previous example because you can always save a 32 bit signed integer into a 64 bit signed integer).
For unsafe conversions (ie, conversions where you loose precision), you need to perform an explicit cast. For instance, if you try to convert a Single into an Int32, you’ll need to perform a cast:
Single val = 10.9f;
Int32 myInt = (Int32) val;
In C#, myInt will end up with the value 10 since the compiler will simply truncate the Single’s value. There’s still one additional feature we have with primitive types: they can be written as literals. A literal is *always* an instance of a type and that’s why you can access its type member directly. Here’s an example:
String val = "Hello There".Substring(0, 5);
Finally, the compiler will also let you use several operators against primitive types and it will be responsible for interpreting them. For instance, you can write the following code:
Int32 someVal = 30;
Int32 res = 10*someVal;
There’s still an interesting discussion left (how to handle overflows), but I’ll leave it for a future post. Stay tuned for more.