Reference Types and value Types in c#
Reference Types and value types
The CLR (Common Language Runtime) supports two flavors of types: reference types and value types. Most types in FCL (Framework Class Library) are reference types. Memory for reference types is always allocated from managed heap, and the C# new operator returns the memory address where the object itself. Consider the following when working with reference types circumstances related to application performance:
- memory for reference types is always allocated from the managed heap;
- every object allocated on the heap contains additional members to be initialized;
- the object bytes unoccupied with useful information are set to zero (this applies to fields);
- Placing an object on the managed heap over time triggers a build litter.
If all types were referencing, application performance would plummet. Imagine how much the application would slow down if memory was allocated every time an Int32 value was accessed! Therefore, in order to accelerate Handle simple, commonly used types The CLR offers “lightweight” types are significant. Instances of these types are usually placed on the thread’s stack (although they can be embedded in a reference type object as well). In representing an instance of a variable has no pointer to an instance; instance fields are allocated in the variable itself. Since the variable contains the fields of the instance, then to work Instance bots do not need to dereference the instance. Thanks to, that instances of value types are not handled by the cleaner debris, the intensity of the managed heap is reduced and the number of garbage collection sessions the application needs during its existence.
In the .NET Framework documentation, you can see at a glance which types are referenced and which are significant. If a type is called a class, speech it is about a reference type. For example, the System.Object classes, System.Exception, System.IO.FileStream and System.Random are reference types. In turn, meaningful types are called structures and enumerations in the documentation. For example, the structures System.Int32, System.Boolean, System.Decimal, System.TimeSpan, and System.DayOfWeek enumerations, System. IO.FileAttributes and System.Drawing.FontStyle are both value types.
All structures are direct descendants of the abstract type System.ValueType, which in turn derives from System.Object. By default, all value types must derive from System.ValueType. All enumerations derive from the System.Enum type, which derives from System.ValueType. CLR and programming languages in different ways work with enumerations.
When defining your own value type, you cannot choose an arbitrary base type, however, a value type can implement one or more selected the interfaces you have. Also, in the CLR, the value type is isolated, that is, it cannot serve as a base type for any other reference or a significant type. Therefore, for example, it is impossible to indicate in the description of a new type as base types Boolean, Char, Int32, Uint64, Single, Double, Decimal, etc.
Note:
Many developers (in particular, those who write unmanaged C / C ++ code) the division into reference and value types seems strange at first. In uncontrollable In C / C ++ code, you declare a type, and the code already decides where to place the instance type: on the thread stack or on the application heap. Differently in managed code: developer, a descriptive type, specifies where instances of a given type should be placed, and a developer using a type in their code has no control over it.
The following code demonstrates the difference between reference and value types:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
// Reference type (since 'class') class SomeRef {public Int32 x; } // Valid type (since 'struct') struct SomeVal {public Int32 x; } static void ValueTypeDemo () { SomeRef r1 = new SomeRef (); // Allocated on the heap SomeVal v1 = new SomeVal (); // Placed on the stack r1.x = 5; // Dereference the pointer v1.x = 5; // Change in the stack Console.WriteLine (r1.x); // Displays "5" Console.WriteLine (v1.x); // Also displays "5" // execute the previous lines SomeRef r2 = r1; // Only the link (pointer) is copied SomeVal v2 = v1; // Push and copy members r1.x = 8; // Changes r1.x and r2.x v1.x = 9; // V1.x changes, but 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" // execute ALL previous lines } |
In this example, the SomeVal type is declared with the struct keyword, and no more the common keyword class. In C#, types declared as struct are are significant, and those declared as class are referential. Between behaviors, there are significant differences between reference and value types. So so it is important to understand which family a particular type belongs to – reference or value: after all, this can significantly affect the way you express their intentions in the code.
The previous example has the following line:
SomeVal v1 = new SomeVal (); // Placed on the stack
It might appear that the SomeVal instance will be pushed onto the managed heap. However, since the C# compiler “knows” that SomeVal is a significant type, in the code it generates, an instance of SomeVal will be pushed onto the thread’s stack. # also ensures that all fields of an instance of a value type are cleared.
The same line can be written differently:
SomeVal v1; // Placed on the stack
Here, too, IL code is generated that pushes the SomeVal instance onto the stack. current and zeroes all its fields. The only difference is that the instance created is new, C# “considers” initialized. I will explain this thought at following example:
// The next two lines are compiled because C# thinks
// that the fields in v1 are zero-initialized
SomeVal v1 = new SomeVal ();
Int32 a = v1.x;
// The following lines will cause a compilation error because C # doesn’t think
// that the fields in v1 are zero-initialized
SomeVal v1;
Int32 a = v1.x;
// error CS0170: Use of possibly unassigned field ‘x’
// (error CS0170: Used field ‘x’, which is not assigned a value)
When designing your type, check whether to use instead of a reference type significant. This can sometimes improve the efficiency of your code. Especially said is valid for a type that satisfies all of the following conditions.
A type behaves like a primitive type. In particular, this means that the type simple enough and has no members that can change instance type fields, in this case, say that the type is immutable. In fact in fact, it is recommended to mark many significant types with readonly specifier
A type does not need to have any other type as its base.
The type has no types derived from it.
You also need to consider the size of the type instances because, by default, arguments are passed by value; while the fields of instances of the significant types are copied, which negatively affects performance. I repeat: for a method that returns a value type, the instance fields are copied into memory, allocated by the caller at the place of return from the method, which reduces the efficiency work of the program. Therefore, in addition to the listed conditions, declare a type as significant if at least one of the following conditions is true:
- The size of the instances of the type is small (about 16 bytes or less).
- The size of type instances is large (more than 16 bytes), but instances are not transferred as method parameters or are not return values from the method.
The main benefit of value types is that they are not allocated on the managed heap. Of course, compared to reference types, value types have limitations. The most important differences between significant and reference types are:
- Significant objects come in two forms: unboxed and boxed. Reference types are only in packaged form.
- Value types derive from System.ValueType. This type has the same methods as System.Object. However, System.ValueType overrides Equals method that returns true if field values in both objects match up. In addition, the GetHashCode method is overridden in System.ValueType, which creates a hash code according to the algorithm, taking into account the values of the fields of the object instance. Due to performance issues in the default implementation, when defining your own value types, you must override and write your own implementation of the Equals and GetHashCode methods.
- Since the declaration of a new value or reference type cannot be specified to create a value type as a base class, create new ones in a value type virtual methods are not allowed. Methods cannot be abstract and implicit are sealed (that is, they cannot be overridden).
- Reference variables contain the addresses of objects on the heap. When a variable of a reference type is created, it is assigned null by default, then is, at this moment it does not point to a valid object. An attempt to use a variable with such a value will result in an exception being thrown NullReferenceException. At the same time, in a variable of a value type, always contains some value of the corresponding type, and during initialization all members of this type are assigned 0. Since the variable is significant type is not a pointer, when accessing a value type, an exception NullReferenceException cannot be thrown. CLR supports the concept a value type of a special kind that can be assigned null (nullable types).
- When a variable of a value type is assigned to another variable of a value type type, all its fields are copied. When a variable of a reference type is assigned to a variable of a reference type, only that variable is copied address.
- As a result of what was said in the previous paragraph, several reference variables types can refer to one object on the heap, due to which, working with one variable, you can change the object referenced by another variable. At the same time, each value type variable has its own copy data of the “object”, therefore, operations with one variable of a value type are not affect another variable.
- Since unboxed value types are not allocated on the heap, them, memory is freed immediately upon return of control of the method in which the instance of this type is described (as opposed to waiting for garbage collection).