variable
variable

Reputation: 9664

What is the difference between use case of constructor vs init to set the value during object construction?

An init-only setter assigns a value to the property or the indexer element only during object construction. What is the difference between use case of constructor vs init to set the value during object construction?

Sample 1:

public class Person
{
   private string _myName;

   public Person(string myName)
   {
      _myName= myName;
   }

   public string Name => _myName;
}

Sample 2:

public class Person
{
   private string _myName;

   public string Name
     {
         get { _myName; }
         init { _myName= value; }
     }
}

Sample 3 (ignore this sample because it is the same as Sample 2):

public class Person
{
   private string _myName;

   public string Name
     {
         get => _myName; 
         init => _myName= value;
     }
}

Upvotes: 3

Views: 3427

Answers (3)

user107511
user107511

Reputation: 822

The idea is to allow readonly properties that you know that will have a written value only during initialization of the object, but after the initialization the value cannot be written.

If you define a set method instead of init, the value can be written any time during the object's life span.

This allows you to initialize a property without having to pass it directly to the constructor as parameter.

For example:

class A
{
    public int X { get; init;} 
} 

Allows this:

A a = new A() 
{
    X = 3,
};

And trying to write to X after that, won't compile:

a.X = 5;

gives a compilation error:

CS8852  Init-only property or indexer 'A.X' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.

If you don't use init, and use set instead, your object can be written any time.

If you define a readonly property without the init method, you can still initialize the property inside a constructor, but then you have to pass a parameter to the constructor. So the decision to use init depends more of you code styling and where do you hold the parameters for the initialized field, and whether you prefer to pass it to a constructor or not.

Notice that this has nothing to do with field access. If init is defined as public, then it is possible to initialize it from outside of the class. It can also be declared private or protected and then it can be accessed from the class or deriving classes only respectively.

Upvotes: -1

Dai
Dai

Reputation: 155085

The main differences and similarities are described in this table: (including my editorializing...)

Constructor parameter init property
Since C# 1.0 C# 9.0
Are required / Has hard guarantees about being present Yes No
Self-documenting Since C# 4.0 Yes
Can overwrite readonly fields Yes Yes
Suitable for Required and optional values Optional values
Ease of reflection Just use ConstructorInfo Horrible
Supported by MEDI Yes No
Breaks IDisposable No Yes
Class knows init order Yes No
Ergonomics when subclassing Tedious Decent

The downsides of init are mostly inherited from the downsides of C#'s object-initializer expressions which still have numerous issues (in the footnote).

As for when you should vs. shouldn't:

  • Don't use init properties for required values - use a constructor parameter instead.
  • Do use init properties for nonessential, non-required, or otherwise optional values that when set via individual properties do not invalidate the object's state.

  • In short, init properties make it slightly easier to initialize nonessential properties in immutable types - however they also make it easier to shoot yourself in the foot if you're using init for required members instead of using a constructor parameter, especially C# 8.0 nullable-reference-types (as there's no guarantees that a non-nullable reference-type property will ever be assigned).
  • In terms of guidance:
    • If your class is not immutable, or at least does not employ immutable-semantics on certain properties then you don't need to use init on those properties.
    • If it's a struct then don't use init properties at all, due to all the small details in struct copy behavior.
    • In my opinion (not shared by everyone else), I recommend you consider an optional (could also be nullable) constructor parameter or an entire different constructor overload instead of init properties given the problems I feel they have and lack of any real advantages.

Footnote: Problems with C# object-initializer syntax, inherited by init properties:

  • Breaks debugging: Even in C# 9, if any line of the initializer throws an exception then the exception's StackTrace will be the same line as the new statement instead of the line of the sub-expression that caused the exception.
  • Breaks IDisposable: if a property-setter (or initialization expression) throws an exception and if the type implements IDisposable then the newly created instance will not be disposed-of, even though the constructor completed (and the object is fully initialized as far as the CLR is concerned).

Upvotes: 3

Enigmativity
Enigmativity

Reputation: 117057

Let's start off by looking at the anatomy of an init property.

Take these two simple examples:

public class Foo
{
    public string Name { get; set; }
}

public class Bar
{
    public string Name { get; init; }
}

Let's look at the IL differences between the two:

.class nested public auto ansi beforefieldinit Foo extends [System.Runtime]System.Object
{
    .method public hidebysig specialname instance string get_Name () cil managed { ... }
    .method public hidebysig specialname instance void set_Name (string 'value') cil managed { ... }

    .property instance string Name()
    {
        .get instance string Foo::get_Name()
        .set instance void Foo::set_Name(string)
    }
}

.class nested public auto ansi beforefieldinit Bar extends [System.Runtime]System.Object
{
    .method public hidebysig specialname instance string get_Name () cil managed { ... }
    .method public hidebysig specialname instance void modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit) set_Name (string 'value') cil managed { ... }

    .property instance string Name()
    {
        .get instance string Bar::get_Name()
        .set instance void modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit) Bar::set_Name(string)
    }
}

The only difference between the two is the modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit).

Both Foo and Bar implement a public getter & setter, just that Bar has a special IsExternalInit attribute on the public setter.

The difference is compiler controlled. The only time that an init property can be set is during construction or within the object initializer.

Let's look at a more complicated example:

public class Electricity
{
    private const double _amps = 4.2;
    public double Amps => _amps;
    
    private readonly double _power;
    public double Power => _power;
    
    private double _volts;
    public double Volts
    {
        get => _volts;
        init
        {
            _volts = value * 2;
            _power = value * _amps;
            //_amps = 99.0; // Not allowed! Can only be set by direct assignment - effectively prior to construction!
        }
    }
    
    public Electricity(double volts)
    {
        this.Volts = volts * 5;
        _power = 9.2;
        //_amps = 42.0; // Not allowed! Can only be set by direct assignment - effectively prior to construction!
    }
    
    public void Danger()
    {
        //_amps = 0.0; // Not allowed! Can only be set by direct assignment - effectively prior to construction!
        //_power = 0.0; // Not allowed! Can only be set in constructor or init
        //this.Volts = -1.0; // Not allowed! Can only be set in constructor or init
        _volts = 0.0; // Allowed!
    }
}

With this, I can create an instance with this code:

Electricity e = new Electricity(1.0) { Volts = 2.0 };

Now this effectively calls new Electricity(1.0) to create the instance of the class, and, since it is the only constructor, I am forced to call it with a parameter for volts. Note that, inside the constructor, I can call this.Volts = volts * 5;.

Before the assignment to e the code in the initializer block is called. It's just assigning 2.0 to Volts - it is the direct equivalent of e.Volts = 2.0; had we not declared Volts with an init setter.

The result is that by the time e gets assigned the constructor and the call to set Volts have both completed.

Now let's try to make this Electricity code more robust. Let's say that I wanted to be able to set any two properties and have the code compute the third.

A naïve and incorrect implementation would be this:

public class Electricity
{
    public double Volts { get; private set; }
    public double Amps { get; private set; }
    public double Watts => this.Volts * this.Amps;

    public Electricity(double volts, double amps)
    {
        this.Volts = volts;
        this.Amps = amps;
    }

    public Electricity(double volts, double watts)
    {
        this.Volts = volts;
        this.Amps = watts / this.Volts;
    }

    public Electricity(double amps, double watts)
    {
        this.Amps = amps;
        this.Volts = watts / this.Amps;
    }
}

But, of course, this doesn't compile because the three constructors signatures are the same.

But we can use init to make an object that works no matter what properties I set (with the exception of Watts in the example below).

public class Electricity
{
    private readonly double? _volts = null;
    private readonly double? _amps = null;
    private readonly double? _watts = null;

    public double Volts
    {
        get => _volts ?? 0.0;
        init
        {
            _volts = value;
            if (_amps.HasValue)
            {
                _watts = _volts * _amps;
            }
            else if (_watts.HasValue)
            {
                _amps = _watts / _volts;
            }
        }
    }

    public double Amps
    {
        get => _amps ?? 0.0;
        init
        {
            _amps = value;
            if (_volts.HasValue)
            {
                _watts = _volts * _amps;
            }
            else if (_watts.HasValue)
            {
                _volts = _watts / _amps;
            }
        }
    }

    public double Watts
    {
        get => _watts ?? 0.0;
        init
        {
            _watts = value;
            if (_volts.HasValue)
            {
                _amps = _watts / _volts;
            }
            else if (_amps.HasValue)
            {
                _volts = _watts / _amps;
            }
        }
    }
}

I can now do this:

var electricities = new[]
{
    new Electricity() { Amps = 2.0, Volts = 3.0 },
    new Electricity() { Watts = 2.0, Volts = 3.0 },
    new Electricity() { Amps = 2.0, Watts = 3.0 },
    new Electricity(),
};

This gives me:

electricities

So, the net result is that constructors are compulsory, but init properties are optional yet must be used at the time of construction before any reference is passed back to the calling code.

Upvotes: 1

Related Questions