This information will be useful to those who only deal with C #, in practice trying to understand the theory without reading the books to the end. This is most likely a margin note, rather than a detailed description and analysis of the topic. So, who cares how structures behave in memory when working with collections in C #, welcome.
Reading Richter in the evenings, I decided to understand every detail in more detail, having a great desire to understand the .Net ideology more deeply. Scrolling through the pages of the book, I came to the conclusion that a lot of things that are projected and written every day constantly violate some of the foundations of .Net.
Consider the example of structures and collections. I will not remind that structures are value-types and behave differently than reference ones.
Example 1. Using the old ArrayList.
')
System.Int32 myInt = 7; ArrayList al = new ArrayList(); al.Add(myInt);
The expected result will be the presence of two different elements in a stack of type System.Int32. Moreover, one of them will already be a reference type. If you open the IL-representation of this code, you will see a fair confirmation:
IL_0002: stloc.0 // myInt
IL_0003: newobj System.Collections.ArrayList..ctor
IL_0008: stloc.1 // al
IL_0009: ldloc.1 // al
IL_000A: ldloc.0 // myInt
IL_000B: box System.Int32
IL_0010: callvirt System.Collections.ArrayList.Add
The box (boxing) operation creates an object on an Int32 heap. When adding an element of the structure type, each time its analog will be created in the heap, and the link will be added to the collection. In time, generic collections came to help this action. Consider an example and its IL representation:
Example 2. Using generalizations for collections:
System.Int32 myInt = 7; List<int> l = new List<int>(); l.Add(myInt);
This code is converted to:
IL_0002: stloc.0 // myInt
IL_0003: newobj System.Collections.Generic.List..ctor
IL_0008: stloc.1 // l
IL_0009: ldloc.1 // l
IL_000A: ldloc.0 // myInt
IL_000B: callvirt System.Collections.Generic.List.Add
Such collections are more perfect and can work with value-types more intelligently, without using box \ unbox operations. This significantly reduces memory costs, increases productivity, and reduces the number of garbage collections.
However, experimenting with the collections for some time in more detail, I came across an unexpected moment. Sometimes there is a need to write something in such a style as:
IList l = new List <T> ();
The code is simple, but its result for structures turned out to be ambiguous.
If we look at the IL representation of such code:
System.Int32 myInt = 7;
IList l = new List <int> ();
l.Add (myInt);
Then we get the following:
IL_0002: stloc.0 // myInt
IL_0003: newobj System.Collections.Generic.List..ctor
IL_0008: stloc.1 // l
IL_0009: ldloc.1 // l
IL_000A: ldloc.0 // myInt
IL_000B: box System.Int32
IL_0010: callvirt System.Collections.IList.Add
We again get to the packaging unit. And again we will get the object in the heap as a copy of our myInt value. Apparently, the developers implemented the collections in such a way that, in fact, an explicit indication of the type of generalization for a variable affects the further behavior of the collection object, especially for significant types.
This was the first conclusion, which tried to explain what was happening. As a result, an explanation was found a little later from the author himself. Any interface variable should always be placed on the heap, which made an additional packing operation appear.
The conclusion is simple. Initially, the theory, and then practice, although it usually happens differently.