SoFunction
Updated on 2025-03-04

C#9.0: Init related summary

background

In previous C# versions, if you need to define an unmodified type, it is generally: declare it as readonly and set it to only include get accessors, not set accessors. as follows:

public class PersonInfo
 {
  /// <summary>
  /// Identity number  /// </summary>
  public string UserCode { get; }

  /// <summary>
  /// Name  /// </summary>
  public string UserName { get; }

  /// <summary>
  /// Initialize assignment  /// </summary>
  /// <param name="_userCode"></param>
  /// <param name="_userName"></param>
  public PersonInfo(string _userCode,string _userName)
  {
   UserCode = _userCode;
   UserName = _userName;
  }
 }

This method is feasible and also achieves our purpose. However, the amount of code is large, and additional construction methods are needed to implement initial assignment. And if there are more fields, the larger the constructor with parameter will be, the greater the development workload, and it will be less difficult to maintain.

To change this state, C# 9.0 provides a solution: configure it as read-only when the object is initially replaced.

Especially useful for creating tree objects with nested structures in one go. The following is a case of user information initialization:

PersonInfo pi = new PersonInfo() { UserCode="1234567890", UserName="Brand" }; 

From this example, to initialize an object, we must first add a set accessor to the attributes that need to be initialized, and then we can achieve it by assigning values ​​to the attributes or indexers in the object initializer. as follows:

public class PersonInfo
 {
  /// <summary>
  /// Identity number  /// </summary>
  public string UserCode { get; set; }

  /// <summary>
  /// Name  /// </summary>
  public string UserName { get; set; }
 }

So for initialization, the properties must be mutable, and the set accessor must exist. This is the problem. In many cases, in order to avoid the property being changed after initialization, immutable object types are required, so the setter accessor is obviously not applicable here.

Based on this common needs and limitations, C# 9.0 introduces an init setting accessor that is only used for initialization. At this time, the above PersonInfo class can be defined as follows:

public class PersonInfo
 {
  /// <summary>
  /// Identity number  /// </summary>
  public string? UserCode { get; init; }

  /// <summary>
  /// Name  /// </summary>
  public string? UserName { get; init; }
 }

By using the init accessor, the code becomes concise and easy to understand, meeting the above read-only needs, and is easier to encode and maintain.

Definition and use

init (only initialize attributes or indexer accessors): It can only be used to assign values ​​when initializing during the object construction stage. It is considered a variant of the set accessor. The position of the set accessor is replaced by init. Init has the following restrictions:

1. The init accessor can only be used in instance properties or indexers, and is not available in static properties or indexers.

2. Properties or indexers cannot contain both init and set accessors

3. If the attribute of the base class has init, then all related rewrites of the attribute or indexer must have init. The same goes for the interface.

When to set up an init accessor

Except in local methods and lambda expressions, properties and indexers with init accessors can be set in the following cases. The timing of these settings is in the object construction stage. After the construction stage, subsequent assignment operations will not be allowed.

1. During the working of the object initializer

2. During the with expression initializer operation

3. In the instance constructor of the type that is located or derived, in this or base use

4. In any attribute init accessor, use this or base

5. In use of attribute with named parameters

Under these restrictions, it means that the PersonInfo we defined above can only be used when the object is initialized, and the second assignment is not allowed.

That is: Once initialization is completed, only initializing attributes or indexes protects the state of the object from changing.

 var person = new PersonInfo() { UserCode="12345678", UserName="Brand" };
 //Tip error: Init-only can only be allocated in object initializer or instance constructor  = "Brand1"; 

init property accessor and read-only fields

Because the init accessor can only be called at initialization, the read-only fields of the enclosed class can be changed in the init property accessor.

It should be noted that assigning values ​​to readonly fields from the init accessor is limited to fields defined in the same type as the init accessor. Through it, it cannot assign values ​​to readonly fields defined in the parent class. Regarding the examples related to this inheritance, we will see in the hierarchical pass between types 2.4.

public class PersonInfo
 {
  private readonly string userCode = "<unknown>";
  private readonly string userName = "<unknown>";

  public string UserCode
  {
   get => userCode;
   init => userCode = (value ?? throw new ArgumentNullException(nameof(UserCode)));
  }
  public string UserName
  {
   get => userName;
   init => userName = (value ?? throw new ArgumentNullException(nameof(UserName)));
  }
 }

Transmission between types levels

We know that properties or indexers that only contain get accessors can only be initialized in the class itself, but the rules that init accessors can set can be passed across type levels.

As long as a member with an init accessor is accessible, the object instance can be known at the construction stage, then this member is set.

1. Use in object initialization is allowed

public class PersonInfo
 {
  /// &lt;summary&gt;
  /// Identity number  /// &lt;/summary&gt;
  public string UserCode { get; init; }

  /// &lt;summary&gt;
  /// Name  /// &lt;/summary&gt;
  public string UserName { get; init; }

  public PersonInfo()
  {
   UserCode = "1234567890";
   UserName = "Brand";
  }
 }

2. It is also allowed in the instance constructor of the derived class, such as the following two examples:

public class PersonInfoExt : PersonInfo
 {
  public PersonInfoExt()
  {
   UserCode = "1234567890_0";
   UserName = "Brand1";
  }
 }
var personext = new PersonInfoExt() { UserCode="1234567890_2", UserName="Brand2" };

From the perspective that the init accessor can be called, object instances can be known during the open construction stage. Therefore, in addition to normal sets that can be done, the following behavior of the init accessor is also allowed.

1. Call other available init accessors through this or base

2. The readonly field defined in the same type can be assigned to the value through this

In init, you cannot change the readonly field in the parent class, you can only change the readonly field in this class. The sample code is as follows:

class PersonInfo1
 {
  protected readonly string UserCode_R;
  public String UserCode
  {
   get =&gt; UserCode_R;
   init =&gt; UserCode_R = value; // Correct: The readonly attribute defined in the same class can be assigned directly through this  }
  internal String UserName { get; init; }
 }

 class PersonInfo1Ext : PersonInfo1
 {
  protected readonly int NewField;
  internal int NewProp
  {
   get =&gt; NewField;
   init
   {
    NewField = 100; // correct    UserCode = "123456";  // correct    UserCode_R = "1234567";  // An error occurred, trying to modify the readonly field UserCode_R in the base class   }
  }

  public PersonInfo1Ext()
  {
   UserCode = "123456"; // correct   UserCode_R = "1234567"; // An error occurred, trying to modify the readonly field UserCode_R in the base class  }
 }

If init is used for virtual modified properties or indexers, then all overwrite rewrites must be marked as init and cannot be set. Similarly, it is impossible for us to override a set with init.

public class PersonInfo
 {
  /// &lt;summary&gt;
  /// Identity number  /// &lt;/summary&gt;
  public virtual string UserCode { get; init; }

  /// &lt;summary&gt;
  /// Name  /// &lt;/summary&gt;
  public virtual string UserName { get; set; }
 }

 public class PersonInfoExt1 : PersonInfo
 {
  public override string UserCode { get; init; }
  public override string UserName { get; set; }
 }

 public class PersonInfoExt2 : PersonInfo
 {
  // Error: The init attribute of the base class must be rewritten by init  public override int UserCode { get; set; }
  // Error: The init attribute of the base class must be rewritten by set  public override string UserName { get; init; }
 }

Init is applied in the interface interface

The default implementation in an interface can also be initialized using init. The following is an application pattern example.

interface IPersonInfo
 {
  string Usercode { get; init; }
  string UserName { get; init; }
 }

 class PersonInfo
 {
  void NewPersonInfo&lt;T&gt;() where T : IPersonInfo, new()
  {
   var person = new T()
   {
    Usercode = "1234567890",
    UserName = "Jerry"
   };
    = "111"; // mistake  }
 }

The init accessor is allowed to be used in properties in readonly struct. The goals of init and readonly are consistent, that is, read-only. The sample code is as follows:

readonly struct PersonInfo
 {
  /// &lt;summary&gt;
  /// Identity number  /// &lt;/summary&gt;
  public string UserCode { get; init; }

  /// &lt;summary&gt;
  /// Name  /// &lt;/summary&gt;
  public string UserName { get; set; }
 }

But it is important to note:

1. Whether it is a readonly structure or a non-readonly structure, whether it is manually defined or automatically generated attributes, init can be used.

2. The init accessor itself cannot be marked readonly. But the property or indexer located can be marked readonly

struct PersonInfo
 {
  /// &lt;summary&gt;
  /// Identity number  /// &lt;/summary&gt;
  public readonly string UserCode { get; init; }

  /// &lt;summary&gt;
  /// Name  /// &lt;/summary&gt;
  public string UserName { get; readonly init; }
 }

The above is the detailed content of the relevant summary of C#9.0:Init. For more information about C#9.0:Init, please follow my other related articles!