The default constructor is a fairly simple construction, which comes down to the creation of a constructor with no parameters. So, for example, if you declare a non-static class and do not declare a custom constructor (no matter with or without parameters), the compiler will independently generate a constructor without parameters. However, when it comes to default constructors for structures (for significant types), then everything becomes not so simple.
Here is a simple example of how you answer the following question:
how many of the relevant types are from. NET Framework contains default constructors? The intuitive answer seems to be “
everything ”, and you will be wrong, because in fact,
none of the significant types. NET Framework does not contain a default constructor .
But let's take everything in order and start by comparing such concepts as the default constructor and the default values.
')
When it comes to classes, everything is simple: if the constructor is not explicitly declared, the C # compiler will generate a parameterless constructor, which is called the default constructor; the default value of any variable (or field) of the reference type is
null .
But when it comes to structures, then everything becomes a bit more complicated. The default value of an instance of a significant type is the “zero” representation of all its fields, i.e. all numeric fields are 0, and all reference fields are
null . From the point of view of the C # language, the default constructor of a meaningful type does the same thing - it returns an instance of the structure with a default value. This means that the following code is equivalent:
Size size1 = new Size(); Size size2 = default(Size);
In this case, for both lines of code, the compiler will generate the same
iniobj instruction, which will lead to an identical result in both cases - resetting all the fields in the
Size structure.
The C # language specification says that the user is not allowed to create a default constructor explicitly, since any structure contains it implicitly. However, this is not quite true: if we get a list of
Size constructors, we will not see a constructor without parameters. What is called the default constructor in C # is actually a “zeroing” of the object and is not a constructor in the usual sense of the word and does not contain any specialized code in the
Size type.
Similar mixing of concepts exists not only when calling the operator
new , but also when initializing structure fields in the constructor. So, for constructors of structures, the same rules of obligatory initialization of all fields of the structure are applied, similar to the rules for local variables (definite assignment rules). This means that before the completion of the body of the constructor, all fields of the structure must be explicitly or implicitly initialized:
struct SomeStruct { private int _i; private double _d; public SomeStruct(int i) : this()
Calling
this () looks exactly like calling the default constructor and prevents a compilation error due to the fact that it resets (and therefore initializes) all fields of the structure. In fact, the call to
this () turns into the same
initobj instruction used earlier to get the default value of the structure instance.
Such a mix of default constructor concepts to obtain a default value for significant types is common on the .NET platform, but is not required. Some languages, such as “bare” IL or Managed C ++, support full-fledged custom constructors for meaningful types that allow you to initialize the state of a structure in an arbitrary way, and not just with default values.
Although the C # language does not allow you to create default constructors for structures, it allows you to use them. When the C # compiler encounters the instruction “
new SomeType ”, the generated code depends on whether the specified type is a class or structure, and also on whether the structure contains a full default constructor or not:
StringBuilder sb = new StringBuilder();
In the first case, the default constructor of the
StringBuiilder class will be called using the
newobj instruction, but the result of creating an instance of a meaningful type depends on its implementation.
Both the
Size and
CustomValueType types are significant types, and the
CustomValueType type contains a default constructor (we will see later how to achieve this). In line 2, the
size variable is initialized with default values ​​using the
initobj instruction, and in line 3 the constructor of the
CustomValueType type is
invoked using the
call Custom CustomValueType ..ctor instruction .
Creating a structure with a default constructor
To generate the structure, we will use the
System.Reflection.Emit module, which supports all the necessary functionality. The process of creating a new type begins creating the
AssemblyBuilder object, inside which a
DynamicModuleBuilder instance is
created , in which a type is already being created. This order is explained by the fact that the assembly, in fact, contains only the metadata of the assembly (dependencies, etc.) and modules, which, in turn, already contain custom types.
public static Type GenerateValueTypeWithDefaultConstructor(string name, string outputString = "Hello, value type's default ctor!") {
The
GenerateValueTypeWithDefaultConstructor method takes the type name and the string displayed on the console (the line is needed to write a unit test that checks the operation of this method). After that, we generate a simple meaningful type with a constructor invoking the
Console .WriteLine with the specified string.
After that, we can create this type using
Activator.CreateInstance or save the generated assembly with
AssemblyBuilder.Save and use it in the usual way. After that, it will be possible to use the generated type in the usual way and calmly call the default constructor of a significant type:
Default constructor invocation rules
There are several points of use of the C # structures with the current default constructor.
One of the main reasons for the lack of user default constructors for structures is a drop in performance when working with arrays.
var array = new CustomValueType[5];
To create an array, the
newarr instruction is
used ; in this case, all elements of the array are initialized with default values, which in the case of significant types means “zeroing” of all fields of all array instances. Even if the type used contains a real default constructor (as in our case), in the interests of performance they will not be called anyway.
To call the constructor of all elements, you need to do this explicitly:
But even if the developers of the CLR and the languages ​​of the .NET platform would go to a drop in performance by invoking dozens of custom designers, this would not have saved all problems.
Let's look at the following code:
var list = new List<CustomValueType>(50);
This constructor takes a capacity (capacity) of the list, which means that an array of the appropriate size will be created inside it, which, in turn, will cause at least 50 default constructors to be called, although the current size of the list will still be 0. To solve this problem, would separate the process of allocating memory for an array from the process of creating an instance of a significant type inside it. Such a technique is used in the C ++ language with the help of the
host operator new , but this would clearly add more problems than good, so it’s reasonable enough that the .NET developers didn’t go for it.
NOTEOn the .NET platform, the problem with lists, arrays, and enums still exists. Details can be found in the article
"Problems of transferring a list of listings or Why abstractions flow .
"Working with arrays is not the only place where an existing constructor of a significant type will not be called. The following table makes it clear when such a constructor will be called, and when not.
I want to draw attention to the mismatch of behavior in the following case: it is known that instantiating a generic parameter occurs using
Activator . However, when creating an instance of the
CustomValueType type using
Activator .CreateInstance, our default constructor will be invoked, but when calling the
CreateWithNew method and creating an instance of a significant type using
new T () , it will not.
Conclusion
So, we found out the following:
- The default constructor in C # is the instruction for zeroing the value of an object.
- From the point of view of the CLR, default constructors exist and the C # language even knows how to call them.
- The C # language does not allow creating custom constructors by default for structures, since this would lead to a drop in performance when working with arrays and significant confusion.
- Working in languages ​​that support the creation of constructors by default, they still do not need to be declared for the same reasons that they are prohibited in most languages ​​of the .NET platform.
- Significant types are not as simple as they seem: in addition to problems with variability (mutability), significant types even with default constructors and that is not all simple.