Hey Nathan, good question I hadn't thought of those types like that.
Lets look at a quick example in C#.
using System;
namespace TestCLIReferenceTypes
{
class Program
{
static void Main(string[ args)
{
Object obj = new Object();
Int32 int32 = 100;
uint rint = 200;
for (; rint > 100 & int32 > 0; rint--, int32--)
{
Console.WriteLine("{0}-{1}:{2}", rint, int32, obj);
}
}
}
}
Now this sample is a bit contrived only because you have to actually use the variables or the compiler just optimizes them away. We simply have 3 variables; an Object, a System.Int32 and an int; one which we know is a reference type (Object).
Lets see what the compiler does with our code; this is the IL that is created (I'll comment inline):
.method private hidebysig static void Main(string[ args) cil managed
{
.entrypoint
.maxstack 4
.locals init (
[0] object obj, // We have 4 local variables
[1] int32 int32, // already this is looking interesting, they both turned out as "int32".
[2] int32 rint, // lets look further to see if they are value types
[3] bool CS$4$0000)
L_0000: nop
L_0001: newobj instance void [mscorlib]System.Object::.ctor() // here we see the reference type created by the newobj opcode
L_0006: stloc.0 // which calls the class constructor and puts a reference to the object on the stack
L_0007: ldc.i4.s 100 // ah, the Int32 is loaded, but its value is directly loaded onto the stack instead of a reference
L_0009: stloc.1
L_000a: ldc.i4 200 // same with our int
L_000f: stloc.2
L_0010: br.s L_0034
L_0012: nop
L_0013: ldstr "{0}-{1}:{2}" // an aside: strings are special-cased in the CLI, though here it kind of looks like the value assignments above
L_0018: ldloc.2 // they are always allocated in a string heap
L_0019: box int32 // another tell tale sign of value types, boxing
L_001e: ldloc.1 // the WriteLine takes object parameters so the ints need to be boxed
L_001f: box int32
L_0024: ldloc.0
L_0025: call void [mscorlib]System.Console::WriteLine(string, object, object, object)
L_002a: nop
-- SNIP looping code --
L_0042: ret
}
Here is the reasoning for this. Microsoft defined the Common Language Infrastructure (CLI) and got it standardized by the ECMA (ECMA-335). The CLI is a specification where as the .NET framework is an implementation of the CLI. The subset of the CLI that describes what types can be used in a language is called the Common Language Specification (CLS). Microsoft's Common Language Runtime (CLR), the infrastructure that all .NET languages run ontop of, implements the CLS.
In the CLR, the types that you see as System.Int32, System.Int64, etc. are implemented in mscorlib as outlined in the CLS. In fact, the C# specific type names such as int, short, object, and long are not CLS types at all, but aliases that the compiler maps at compile-time to the CLS types that the framework implements.
Another interesting tidbit in that vien is that the CLR actually extends on the CLS and implements implements the capabilities for non-CLS types to be exposed in compilers, see the unsigned types in C# for example; uint, ushort, and ulong. These types are implemented in the CLR and exposed in C# even though they are not defined in the CLS. There are a couple consequences of using these types, the first being that your code would not be able to run on a runtime strictly implemented to the CLS. Another consequence is that a language that doesn't surface those types will not be able to interact with your code even if that language is running on the CLR.
Hope that helps answer your question and explains a little more about how the compiler targets your code at the CLR.
Have a good one,
Josh