Reputation: 12013
Structs cannot contain explicit parameterless constructors. Such as:
public struct Person
{
public string Name { get; }
public int Age { get; }
public Person() { Name = string.Empty; Age = 0; }
}
However, this is allowed:
public struct Person
{
public string Name { get; }
public int Age { get; }
public Person(string name = null, int age = 0) { Name = name; Age = age; }
}
Any ideas why? Any reason this is bad to do?
Upvotes: 1
Views: 108
Reputation: 18013
The second one is allowed because it is not parameterless. But I wouldn't use optional parameters here because it is very confusing - if you call new Person()
, your constructor will not be executed (you can check it if you replace the default values other than null and zero):
public struct Person
{
public string Name { get; }
public int Age { get; }
public Person(string name = "Bob", int age = 42)
{
Name = name;
Age = age;
}
}
So new Person()
is the same as default(Person)
, both will use the initobj
MSIL instruction instead of calling a constructor.
So why would it be a problem if you could define a default constructor for structs? Consider the following examples.
private void Test()
{
Person p1 = new Person(); // ok, this is clear, use ctor if possible, otherwise initobj
Person p2 = default(Person); // use initobj
Person p3 = CreateGeneric<Person>(); // and here?
Person[] persons = new Person[100]; // do we have initialized persons?
}
public T CreateGeneric<T>() where T : new()
{
return new T();
}
So real parameterless constructors are not allowed for structs in C# (in CLR it is supported, though). Actually parameterless constructors were planned to be introduced in C# 6.0; however, it caused so many compatibility problems that at the end this feature was removed from the final release.
Starting with C# 10 parameterless struct constructors are actually supported due to struct records with field initializers. From the docs:
If a record struct does not contain a primary constructor nor any instance constructors, and the record struct has field initializers, the compiler will synthesize a public parameterless instance constructor.
But not as if everything was obvious now. Let's revisit the examples above:
new Person()
: Even this case is a bit confusing. Obviously, if you have a parameterless constructor, it will call that one. But unlike in case of classes, if there is no parameterless constructor, it will use the initobj
instruction even if there is a constructor overload with optional parameters only (the OP's 2nd example).default(Person)
: This is clear, the initobj
instruction will be usedCreateGeneric<Person>()
: It turns out that it also invokes the parameterless struct constructor... well, at least for the first time. But when targeting .NET Framework, it fails to invoke the constructor for subsequent calls.new Persons[100]
: No constructor call (which is actually expected)And the feature has some further unexpected implications:
new MyStruct()
does not initialize the fields.void Method(Person p = new Person())
also fails to call the parameterless constructor. In the meantime this was 'fixed' by emitting CS1736 if there is a parameterless constructor; otherwise, it is allowed and means just default
.Activator.CreateInstance(Type)
also works incorrectly (behaves the same way as CreateGeneric<Person>()
above): the constructor is invoked only for the first time.Expression.New(Type)
also works incorrectly, not just in .NET Framework but on all .NET/Core platforms prior version 6.0And the story is not over. Now it seems that auto-synthesizing parameterless constructor will be removed, which makes the first bullet point above illegal and will be a breaking change from C# 10. But there are also further open issues such as this one, which also needs some changes to language specification.
Upvotes: 3
Reputation: 13495
A parameter-less constructor for a struct will make it more tempting to create mutable structs which are considered evil.
var person = new Person();
person.Age = 35;
...
I am sure there are other reasons, but a major pain is that because they are copied as they are passed around and it is easy to change the wrong struct and therefore easier to make an error that is difficult to diagnose.
public void IncreaseAge(Person p)
{
p += 1; // does not change the original value passed in, only the copy
}
Upvotes: 1
Reputation: 14846
The values of optional arguments are hardcoded into the calling code.
If later you choose the change your defaults, all compiled code will retain the values it was compiled with.
Upvotes: 0
Reputation: 10777
Any ideas why?
This constructor is not implicitly parameterless -- it has 2 parameters.
Any reason this is bad to do?
Here is a reason: The code can be hard to reason about.
You may expect that if you wrote:
var people = new Person[100];
that all People
in that array would have the default values for the optional arguments but this is untrue.
Upvotes: 0