First, let’s talk about Reference Types and Value Types. I think people don’t really understand the differences and benefits of both. They usually say reference types store content on the heap and value types store content on the stack, which is wrong. Let’s discuss the real differences: However, there are common features: Let’s look closer at each feature.  Let’s look closer at each feature. The main difference between the two types is as follows: This means that assigning or passing a parameter to a method will copy the value. Even if you change the copy, the original will stay the same. However, if you change reference type fields, this will “affect” all parts with a reference to an instance of a type. Let’s look at the
example: It seems this property produces ambiguous code constructs such as the
change of code in collections: There is a small trick in this code. It looks like that we get the structure instance first, and then assign a new value to the Data field of the copy. This means we should get 5 again when checking the value. However, this doesn't happen. MSIL has a separate instruction for setting the values of fields in the structures of an array, which increases performance. The code will work as intended: the program will
output 4 to a console. Let’s see what will happen if we change this code: The compilation of this code will fail, because when you write list[0].Data = 4 you get the copy of the structure first. In fact, you are calling an instance method of the List<T> type that underlies the access by an index. It takes the copy of a structure from an internal array (List<T> stores data in arrays) and returns this copy to you from the access method using an index. Next, you try to modify the copy, which is not used further along. This code is pointless. A compiler prohibits such behavior, knowing that people misuse value types. We should rewrite this example in the following way: This code is correct despite its apparent redundancy. The program will
output 4 to a console. The next example shows what I mean by “the value of a structure is an
entire structure” Both examples are similar in terms of the data location in memory, as the value of the structure is the entire structure. It allocates the memory for itself where it is. These examples are also similar in terms of the elements’ location in memory as the structure takes a defined place among class fields. I don’t say they are totally similar as you can operate structure fields using structure methods. Of course, this is not the case of reference types. An instance itself is on the unreachable Small Object Heap (SOH) or the Large Object Heap (LOH). A class field only contains the value of a pointer to an instance: a 32 or 64-bit number. Let’s look at the last example to close the issue. In terms of memory both variants of code will work in a similar way, but not in terms of architecture. It is not just a replacement of a variable number of arguments. The order changes because method parameters are declared one after another. They are put on the stack the similar way. However, the stack grows from higher to lower addresses. That means the order of pushing a structure piece by piece will be different from pushing it as a whole. The next big difference between the two types is the lack of virtual
methods table in structures. This means that: The lack of a virtual methods table subtracts a certain part of inheritance “magic” from structures but gives them other advantages. The first one is that we can pass instances of such a structure to external environments (outside .NET Framework). Remember, this is just a memory
range! We can also take a memory range from unmanaged code and cast a type to our structure to make its fields more accessible. You cannot do this with classes as they have two inaccessible fields. These are SyncBlockIndex and a virtual methods table address. If those two fields pass to unmanaged code, it will be dangerous. Using a virtual methods table one can access any type and change it to attack an application. Let’s show it is just a memory range without additional logic. Here, we perform the operation that is impossible in strong typing. We cast one type to another incompatible one that contains one extra field. We introduce an additional variable inside the Main method. In theory, its value is secret. However, the example code will output the value of a variable, not found in any of the structures inside the Main() method. You might consider it a breach in security, but things are not so simple. You cannot get rid of unmanaged code in a program. The main reason is the structure of the thread stack. One can use it to access unmanaged code and play with local variables. You can defend your code from these attacks by randomizing the size of a stack frame. Or, you can delete the information about EBP register to complicate the return of a stack frame. However, this doesn't matter for us now. What we are interested in this example is the following. The "secret" variable goes before the definition of hh variable and after it in WidthHolder structure (in different places, actually). So why did we easily get its value? The answer is that stack grows from right to left. The variables declared first will have much higher addresses, and those declared later will have lower addresses. Both data types have another feature which is not plain to see and can explain the structure of both types. It deals with calling instance methods. Logically, we can decide that the method has one compiled body. In other words, there is no instance of a type that has its own compiled set of methods, similar to the sets of other instances. However, the called method knows which instance it belongs to as a reference to the instance of a type is the first parameter. We can rewrite our example and it will be identical to what we said before. I’m not using an example with virtual methods deliberately, as they have another procedure. I should explain the use of the ref keyword. If I didn’t use it, I would get a copy of the structure as a method parameter instead of the original. Then I would change it, but the original would stay the same. I would have to return a changed copy from a method to a caller (another copying), and the caller would save this value back in the variable (one more copying). Instead, an instance method gets a pointer and use it for changing the original straight away. Using a pointer doesn’t influence performance as any processor-level operations use pointers. Ref is a part of the C# world, no more. Both structures and classes have another capability to point to the offset of a particular field in respect to the beginning of a structure in memory. This serves several purposes: General remarks Using FieldOffset to skip unused structure fields The structures coming from the unmanaged world can contain reserved fields. One can use them in a future version of a library. In C/C++ we fill these gaps by adding fields, e.g. reserved1, reserved2,… However, in .NET we just offset to the beginning of a field by using the FieldOffsetAttribute attribute and . A gap is occupied but unused space. The structure will have the size equal to 132 and not 40 bytes as it may seem from the beginning. Union Using the FieldOffsetAttribute you can emulate the C/C++ type called a union. It allows to access the same data as entities of
different types. Let’s look at the example: You might say such behavior is possible only for value types. However, you can simulate it for reference types, using one address for overlapping two reference types or one reference type and one value type: I used a generic type for overlapping on purpose. If I used usual
overlapping, this type would cause the TypeLoadException when loaded in an application domain. It might look like a security breach in theory (especially, when talking about application plug-ins), but if we try to run this code using a protected domain, we will get the same TypeLoadException. Another feature that differentiates both types is memory allocation for objects or structures. The CLR must decide on several things before allocating memory for an object. What is the size of an object? Is it more or less than 85K? If it is less, then is there enough free space on the SOH to allocate this object? If it is more, the CLR activates Garbage Collector. It goes through an object graph, compacts the objects by moving them to cleared space. If there is still no space on the SOH, the allocation of additional virtual memory pages will start. It is only then that an object gets allocated space, cleared from garbage. Afterwards, the CLR lays out SyncBlockIndex and VirtualMethodsTable. Finally, the reference to an object returns to a user. If an allocated object is bigger than 85K, it goes to the Large Objects Heap (LOH). This is the case of large strings and arrays. Here, we must find the most suitable space in memory from the list of unoccupied ranges or allocate a new one. It is not quick, but we are going to deal with the objects of such size carefully. Also, we are not going to talk about them here. There are several possible scenarios for RefTypes: Such operations are rare and can’t compete with ValTypes. The algorithm of memory allocation for value types doesn’t exist. The allocation of memory for value types costs nothing. The only thing that happens when allocating memory for this type is setting fields to null. Let’s see why this happens: 1. When one declares a variable in the body of a method, the time of memory allocation for a structure is close to zero. That is because the time of allocation for local variables doesn’t depend on their number; 2. If ValTypes are allocated as fields, Reftypes will increase the size of the fields. A Value type is allocated entirely, becoming its part; 3. As in case of copying, if ValTypes are passed as method parameters, there appears a difference, depending on the size and location of a parameter. However, that doesn’t take more time than copying one variable into another. Let’s discuss the advantages and disadvantages of both types and decide on their use scenarios. A classic principle says we should choose a value type if it is not larger than 16 bytes, stays unchanged during its life and is not inherited. However, choosing the right type means reviewing it from different perspectives basing on scenarios of future use. I propose three groups of criteria: Each designed feature should reflect its purpose. This doesn’t deal with its name or interaction interface (methods, properties) only. One can use architectural considerations to choose between value and reference types. Let’s think why a structure and not a class might be chosen from the type system architecture's point of view. If your designed type is agnostic to its state, this will mean its state reflects a process or is a value of something. In other words, an instance of a type is constant and unchangeable by nature. We can create another instance of a type based on this constant by indicating some offset. Or, we can create a new instance by indicating its properties. However, we mustn’t change it. I don’t mean that structure is an immutable type. You can change its field values. Moreover, you can pass a reference to a structure into a method using the ref parameter and you will get changed fields after exiting the method. What I talk here about is architectural sense. I will give several examples. If your designed type is an inseparable part of an external type but is integral structurally. That means it is incorrect to say the external type refers to an instance of an encapsulated type. However, it is correct to say that an encapsulated type is a part of an external together with all its properties. This is useful when designing a structure which is a part of another structure. Basing on what I’ve said, here is some advice on using structures: That’s why the choice of a data type is not an obvious process. Often, this can refer to a premature optimization, which is not recommended. However, if you know your situation falls within above stated principles, you can easily choose a value type. Семинары по платформе .NET CLRium