The CLR via C # of Jeffrey Richter has been studying. It takes two months to get up in the morning for an hour. Now we can see chapter 14. I found that the previous content was almost forgotten, so I plan to write something similar to Reading Notes and write down the dry goods for future reference. Some people may not know what the title of CLR via C # means. According to my understanding over the past few months, C # should be used to understand what CLR means.
Value Type and reference type
To put it bluntly, CLR supports two types: Value Type and reference type. It is a classic description of the value type and reference type of MSDN.
Note that the reference types are all memory allocated from the managed stack, and the memory address is returned using the New keyword. When using references, consider the following facts:
The memory of the reference type must be allocated from the managed stack.
Each object allocated on the heap has some additional members (additional members include some required items such as Synchronous Index blocks and object pointers), which must be initialized.
Other bytes in the object are always zero.
When an object instance is allocated from the managed stack, a garbage collection may be executed forcibly.
Each time you use a reference type, additional parameters are allocated from the managed stack, which also overloads the GC, which will definitely affect program performance. CLR also provides a lightweight and efficient data structure: value type. The value type is usually allocated on the thread stack [if the value type is embedded in the reference type, it is also allocated to the hosting Stack]. The variable itself contains the instance field, therefore, if the value type does not require a pointer, the thread will be released immediately after the end of the thread, so it will not cause pressure on GC. Let's look at a simple example:
Public static void Go (){
SomeRef r1 = new SomeRef (); // Allocated in heap
SomeVal v1 = new SomeVal (); // Allocated on stack
R1.x = 5; // Pointer dereference
V1.x = 5; // Changed on stack
Console. WriteLine (r1.x); // Displays "5"
Console. WriteLine (v1.x); // Also displays "5"
// The left side of Figure 5-2 reflects the situation
// After the lines above have executed.
SomeRef r2 = r1; // Copies reference (pointer) only
SomeVal v2 = v1; // Allocate on stack & copies members
R1.x = 8; // Changes r1.x and r2.x
V1.x = 9; // Changes v1.x, not v2.x
Console. WriteLine (r1.x); // Displays "8"
Console. WriteLine (r2.x); // Displays "8"
Console. WriteLine (v1.x); // Displays "9"
Console. WriteLine (v2.x); // Displays "5"
// The right side of Figure 5-2 reflects the situation
// After ALL the lines above have executed.
}
The value type is awesome, right? What are the applicable conditions of the type? Generally, a type can be declared as a value type only when the following conditions are met:
Type does not need to be inherited from other types
Type does not need to be inherited by other types
The instance fields of the type are not changed to Members. We recommend that you set all fields of the value type to readonly]
When the preceding three conditions are met, either of the following conditions must be met:
Type instances are small. 16 bytes or less
Instances of the type are large, but are not passed as real parameters of the method or returned from the method.
The value type is simple and efficient, but it also has disadvantages or limitations:
Two representation forms of a value type object: unboxed and boxed. The binning operation of the value type consumes a lot of performance.
[The reference type is always in the packing format. Therefore, for the value type, we will introduce packing and unpacking in detail later]
Value types cannot be inherited by other types (including value types and reference types. Therefore, there cannot be new virtual methods in the value type. methods cannot be abstract and must be closed.
The Value Type always has a default value and cannot be null.
If you assign a value type variable to another value type variable, the value is assigned one by one. When a variable of the reference type is assigned to another variable of the reference type, only the memory address is copied.
Based on the previous one, the value of the value type is not affected by another value type variable. If the application type points to the same managed heap at the same time, any change to the referenced variable will affect other variables.
The value type on the thread stack will be automatically released when the instance is unavailable, so it will not receive the Finalize notification.
Binning and unboxing value types
As mentioned above, both packing and unpacking are for the value type, because the reference type itself is in the "Packing" status. Convert a value type to a reference type. This is the packing operation.
Packing steps:
Allocate memory in the managed heap. [The memory allocation here is the amount of memory required for each field of the Value Type plus the amount of memory required for the additional members (type object pointers and synchronized index blocks) of the hosted object on the stack]
Copy fields of the value type to the new managed heap memory.
Returns the address of the object on the managed stack. The value type is now a reference type.
Packing conversion allows implicit conversion of value-type to reference-type. The following packing conversions exist:
· From any value-type to object type.
· From any value-type to System. ValueType type.
· Any interface-type implemented from any non-nullable-value-type to value-type.
· From any nullable-type to any interface-type implemented by the Basic nullable-type.
· From any enum-type to System. Enum type.
· From any nullable-type with the basic enum-type to System. Enum type.
The process is very complicated. C # compiler will automatically generate IL to complete these operations. You only need to know the principle and then OK. Now we understand how to unpack the box? Most beginners take it for granted that unpacking is the inverse of packing. The binning operation is actually only one step, that is, to obtain the address of the object to be unpacked on the hosting stack. Then copy the values of each field of the object to the instance of the thread stack. Obviously, the price of unpacking is much lower than that of packing.
Unboxing conversion allows explicit conversion of reference-type to value-type. The following box-breaking conversions exist:
· From object type to any value-type.
· From System. ValueType type to any value-type.
· From any interface-type to any non-nullable-value-type that implements the interface-type.
· Any nullable-type of the interface-type is implemented from any interface-type to its basic type.
· From System. Enum type to any enum-type.
· From the System. Enum type to any nullable-type with the basic enum-type.
I have read the example of packing and unpacking:
Public static void Main6 (){
Int32 v = 5;
Object o = v;
V = 123;
Console. WriteLine (v + "," + (Int32) o );
}
Guess how many packing operations have taken place. The answer is three! If you can explain in detail the packing operations of the above Code, you will be familiar with the packing and unpacking operations.
Author: BangQ