I think it is still quite detailed in this article. Let's share it with you.
1. General Type System
In C #, whether a variable is a value or a reference only depends on its data type.
The basic data types of C # are defined in a platform-independent manner. The pre-defined type of C # is not built into the language, but is built into the. NET Framework .. . NET uses a general type System (CTS) to define predefined data types that can be used in the intermediate language (IL. NET Language is eventually compiled into IL, that is, compiled into CTS-type code.
For example, when C # declares an int variable, it is actually an instance of System. Int32 in CTS. This has important significance:
- Ensure the security of the forced type on IL;
- Achieves interoperability between different. NET languages;
- All data types are objects. They can have methods, attributes, and so on. For example:
Int I;
I = 1;
String s;
S = I. ToString ();
The MSDN diagram shows how each type of CTS is related. Note that instances of the type can only be of the value type or custom description type, even if these types have subcategories.
2. Value Type
All value types of C # are implicitly derived from System. ValueType:
Each value type has an implicit default constructor to initialize the default value of this type. For example:
Int I = new int ();
It is equivalent:
Int32 I = new Int32 ();
It is equivalent:
Int I = 0;
It is equivalent:
Int32 I = 0;
When the new operator is used, the system calls the default constructor of a specific type and assigns the default value to the variable. In the preceding example, the default constructor assigns the value 0 to I. MSDN has a complete default table.
For more information about int and Int32, see understanding System. Int32 and int in C #.
All value types are seal, so a new value type cannot be derived.
It is worth noting that System. ValueType is directly derived from System. Object. That is, System. ValueType is a class type rather than a value type. The key is that ValueType overrides the Equals () method to compare the value type by instance value rather than by reference address.
You can use the Type. IsValueType attribute to determine whether a Type is a value Type:
TestType testType = new TestType ();
If (testTypetype. GetType (). IsValueType)
{
Console. WriteLine ("{0} is value type.", testType. ToString ());
}
3. Reference Type
C # has the following reference types:
- Array (derived from System. Array)
- The following types are defined by the user:
- Class: class (derived from System. Object );
- Interface: interface (the interface is not a "thing", so there is no problem where it is derived. Anders said in C # Programming Language that an interface only represents a Convention [contract]);
- Delegate: Delegate (derived from System. delegate ).
- Object (alias of System. Object );
- String: String (alias of System. string ).
We can see that:
- If the reference type is the same as the value type, the struct can also implement interfaces;
- The reference type can be derived from a new type, but the value type cannot;
- The reference type can contain null values and the value type cannot (the null type function allows null to be assigned to the value type );
- The value assignment of the reference type variable only copies the reference to the object, instead of copying the object itself. When a value type variable is assigned to another value type variable, the included values are copied.
For the last one, the string is often obfuscated. I once saw in an earlier version of a book that the String variable is more efficient than the string variable. I also often hear that String is a reference type, string is a value type, and so on. For example:
String s1 = "Hello ,";
String s2 = "world! ";
String s3 = s1 + s2; // s3 is "Hello, world! "
This does look like a value type assignment. Another example is:
String s1 = "";
String s2 = s1;
S1 = "B"; // s2 is still ""
Changing the s1 value does not affect s2. This makes the string look like a value type. In fact, this is the result of operator overloading. When s1 is changed,. NET re-allocates the memory For s1 on the managed heap. The purpose is to implement the string as the reference type as a string in the General Semantics.
4. Deployment of value and reference types in memory
I often hear about it and often see in the book that the value type is deployed on the stack, and the reference type is deployed on the managed stack. In fact, it is not that simple.
All reference types are deployed on the managed stack. This is easy to understand. When you create an application type variable:
Object reference = new object ();
The new keyword will allocate memory space on the managed stack and return the address of the memory space. The reference on the left is located on the stack. It is a reference that stores a memory address, and the memory (in the managed heap) pointed to by this address stores its content (a System. object instance ). For convenience, the reference type is deployed on the hosting platform.
Let's look at the value type. The wording in the C # language specification is "the struct does not require memory allocation on the heap (However, unlike classes, structs are value types and do not require heap allocation) instead of allocating memory on the stack ". This is confusing: Where is the value type actually deployed?
Array 4.1
Consider Arrays:
Int [] reference = new int [100];
According to the definition, arrays are all reference types, so the int array is of course a reference type (that is, reference. GetType (). IsValueType is false ).
The elements in the int array are all int. According to the definition, int Is a value type (that is, reference [I]. GetType (). IsValueType is true ). So is the value type element in the reference type array located on the stack or heap?
If you use WinDbg to view the specific location of reference [I] in the memory, you will find that they are not on the stack, but on the managed stack.
In fact, for Arrays:
TestType [] testTypes = new TestType [1, 100];
If TestType is a value type, a storage space is allocated for 100 value-type Elements on the managed heap at a time, and the 100 elements are automatically initialized, store the 100 elements in the memory.
If TestType is a reference type, a space is allocated to testTypes in the managed heap, and no elements are automatically initialized (testTypes [I] is null ). When code initializes an element in the future, the storage space of the referenced element will be allocated to the managed stack.
4.2 type nesting
What is more confusing is that the reference type contains the value type and the value type contains the reference type:
Simply look at valueTypeStructInstance, which is a struct instance and seems to be thrown to the stack as a whole. However, the field _ referenceTypeField is of the reference type, and the local variable referenceTypeLocalVarible is also of the reference type.
ReferenceTypeClassInstance has the same problem. The referenceTypeClassInstance itself is a reference type and should be deployed on the hosting stack as a whole. But the field _ valueTypeField is a value type, and the local variable valueTypeLocalVariable is also a value type. Are they on the stack or on the managed stack?
The rule is:
- The reference type is deployed on the managed stack;
- The value type is always assigned to the place it declares: when it is used as a field, it is stored with the variable (Instance) to which it belongs; when it is used as a local variable, it is stored on the stack.
Let's analyze the above Code. For an instance of the reference type, that is, referenceTypeClassInstance:
- From the context, referenceTypeClassInstance is a local variable, so it is deployed on the managed stack and held by a reference on the stack;
- Value Type field _ valueTypeField is part of the referenceTypeClassInstance of the reference type. Therefore, referenceTypeClassInstance of the reference type is deployed on the managed stack (a bit similar to the array );
- ValueTypeLocalVariable is a value type local variable, so it is deployed on the stack.
For Value Type instances, that is, valueTypeStruct:
- According to the context, the value type instance valueTypeStructInstance itself is a local variable rather than a field, so it is located on the stack;
- The reference type field _ referenceTypeField does not have the following problem. It must be deployed on the managed stack and held by a reference (this reference is part of valueTypeStruct and is located on the stack );
- Its reference type local variable referenceTypeLocalVariable is obviously deployed on the managed stack and held by a stack reference.
Therefore, simply put, "The value type is stored on the stack, and the reference type is stored on the managed stack" is incorrect. Specific analysis is required.
5. Use the value type and reference type correctly
This part mainly refers to Objective C #, which is not original to you. I hope that you can better understand the value type and reference type.
5.1 usage of value types and reference types
In C #, we use struct/class to declare a type as value type/reference type.
Consider the following example:
TestType [] testTypes = new TestType [1, 100];
If TestTye is a value type, you only need to allocate it once, and the size is 100 times that of TestTye. If TestTye is of the reference type, it needs to be allocated for 100 times at the beginning. After the allocation, each element value in the array is null, and then 100 elements are initialized. A total of 101 assignments are required for the result. This will consume more time and cause more memory fragments. Therefore, if the type is mainly responsible for data storage, the value type is suitable.
Generally, value types (not supporting polymorphism) are suitable for storing data that is operated by C # applications, and reference types (supporting polymorphism) should be used to define the behavior of applications.
Generally, we create more reference types than value types. If all the answers to the following questions are yes, we should create a value type:
- Is the primary role of this type used for data storage?
- Is the common excuse for this type completely defined by the Access attribute of some data members?
- Are you sure this type can never be a subclass?
- Are you sure this type will never have polymorphism?