Couple of days ago i met with a .net guru and we had discussion on advanced concept of .NET and Memory management; I am sharing the findings of that discussion with you. I feel that many .NET programmers still do not realize the difference between Primitive, Reference and Value types, and how they stored in memory (what goes where) and misuse of any type can lead to subtle bugs that are very hard to find. So it is very important to understand the different types that .NET Framework supports. In this article, I am going to talk about these different types briefly.
"Variables" are simply storage locations for data becuase it is really hard to pragramme against the memory address. It means, a variable is just an association between a name and memory slot. You can place data into them and retrieve their contents as part of a C# expression. The size of that slot, and the interpretation of the value is controlled through "Types" - and this is where the difference between value types and reference types comes in.
Any data types directly supported by the compiler are called primitive types. Primitive types map directly to types that exist in the base class library. For example, in C# an int maps directly to the System.Int32 type. Because of this, the following two lines of code are identical to the two lines of code shown previously:
System.Int32 a = new System.Int32(5);
System.Int32 a = 5;
Above lines are identical to following lines:
int a = new int(5);
int a = 5;
A data type is a value type if it holds the data within its own memory allocation. Value types derive from System.ValueType, which derives from System.Object. Types that derive from System.ValueType have special behavior in the CLR. Value type variables directly contain their values, which means that the memory is allocated inline in whatever context the variable is declared. There is no separate heap allocation or garbage collection overhead for value-type variables. Value types include the following:
- All numeric data types
- Boolean, Char, and Date
- All structures, even if their members are reference types
- Enumerations, since their underlying type is always SByte, Short, Integer, Long, Byte, UShort, UInteger, or ULong
Every structure is a value type, even if it contains reference type members. For this reason, value types such as Char and Integer are implemented by .NET Framework structures.
You can declare a value type by using the reserved keyword, for example, Decimal. You can also use the New keyword to initialize a value type. This is especially useful if the type has a constructor that takes parameters. An example of this is the Decimal(Int32, Int32, Int32, Boolean, Byte) constructor, which builds a new Decimal value from the supplied parts.
public struct TestStruct
public int x, y;
public TestStruct(int p1, int p2)
x = p1;
y = p2;
Value types are sealed, which means, for example, that you cannot derive a type from System.Int32, and you cannot define a struct to inherit from any user-defined class or struct because a struct can only inherit from System.ValueType. However, a struct can implement one or more interfaces. You can cast a struct type to an interface type; this causes a boxing operation to wrap the struct inside a reference type object on the managed heap. Boxing operations occur when you pass a value type to a method that takes a System.Object as an input parameter.
A reference type contains a pointer to another memory location that holds the data. Reference types include the following:
- All arrays, even if their elements are value types
- Class types, such as Form
A class is a reference type. For this reason, reference types such as Object and String are supported by .NET Framework classes.
Note that every array is a reference type, even if its members are value types.
Since every reference type represents an underlying .NET Framework class, you must use the New Operator (Visual Basic) keyword when you initialize it. The following statement initializes an array. The value of a reference type variable is always either a reference or null. If it's a reference, it must be a reference to an object which is compatible with the type of the variable. The slot of memory associated with the variable is just the size of a reference, however big the actual object it refers to might be. (On the 32-bit version of .NET, for instance, a reference type variable's slot is always just 4 bytes.)
Elements That Are Not Types
The following programming elements do not qualify as types, because you cannot specify any of them as a data type for a declared element:
- Properties and procedures
- Variables, constants, and fields
So where are things stored?
The memory slot for a variable is stored on either the stack or the heap. It depends on the context in which it is declared:
- Each local variable (ie one declared in a method) is stored on the stack. That includes reference type variables - the variable itself is on the stack, but remember that the value of a reference type variable is only a reference (or null), not the object itself. Method parameters count as local variables too, but if they are declared with the ref modifier, they don't get their own slot, but share a slot with the variable used in the calling code.
- Instance variables for a reference type are always on the heap. That's where the object itself "lives".
- Instance variables for a value type are stored in the same context as the variable that declares the value type. The memory slot for the instance effectively contains the slots for each field within the instance. That means (given the previous two points) that a struct variable declared within a method will always be on the stack, whereas a struct variable which is an instance field of a class will be on the heap.
- Every static variable is stored on the heap, regardless of whether it's declared within a reference type or a value type. There is only one slot in total no matter how many instances are created. (There don't need to be any instances created for that one slot to exist though.)
There are a couple of exceptions to the above rules - captured variables (used in anonymous methods and lambda expressions) are local in terms of the C# code, but end up being compiled into instance variables in a type associated with the delegate created by the anonymous method. The same goes for local variables in an iterator block.