Lucas B
Lucas B

Reputation: 12013

Strange struct constructor compiler workaround

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

Answers (4)

György Kőszeg
György Kőszeg

Reputation: 18013

The original answer (see the update below):

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.


Update 2022:

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:

  1. 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).
  2. default(Person): This is clear, the initobj instruction will be used
  3. CreateGeneric<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.
  4. new Persons[100]: No constructor call (which is actually expected)

And the feature has some further unexpected implications:

  • If a field has an initializer and there are only parameterized constructors, then new MyStruct() does not initialize the fields.
  • Parameter default value initialization like 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.
  • If you target .NET Framework, 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.0

And 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

NeddySpaghetti
NeddySpaghetti

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

Paulo Morgado
Paulo Morgado

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

Alex Booker
Alex Booker

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

Related Questions