Skip to content

[Version 9.0] Feature support for init accessors - #1452

Draft
BillWagner wants to merge 7 commits into
draft-v9from
v9-init-accessors
Draft

BillWagner wants to merge 7 commits into
draft-v9from
v9-init-accessors

Conversation

@BillWagner

@BillWagner BillWagner commented Nov 6, 2025 •

Copy link
Copy Markdown
Member

This PR contains the work for init accessors in C# 9.

The commits from #978 were squashed to one commit in this branch.

There are a number of comments that haven't been addressed on #978:

Notes:

  • The ANTLR grammar validation fails for one test where the validation is designed to fail – it is testing that right shift is not erroneously recognised – and the checker passes the test if the error message is what it expected. Antlr writes a long informative error message listing all the tokens it might have expected to find at the failing point of the code and that list now has init in it.
  • The renumbering action fails because this feature depends on the grammar in "records".

Comment thread standard/basic-concepts.md Outdated
Comment thread standard/attributes.md Outdated
Comment thread standard/attributes.md Outdated
Comment thread standard/expressions.md
> <td rowspan="6">Property access</td>
> <td><code>P</code></td>
> <td>The get accessor of the property <code>P</code> in the containing class or struct is invoked. A compile-time error occurs if <code>P</code> is write-only. If <code>P</code> is not <code>static</code>, the instance expression is <code>this</code>.</td>
> <td>The get accessor of the property <code>P</code> in the containing class or struct is invoked. A compile-time error occurs if <code>P</code> is write-only or init-only. If <code>P</code> is not <code>static</code>, the instance expression is <code>this</code>.</td>

@RexJaeschke RexJaeschke Nov 7, 2025 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should "containing class or struct" be extended to include interface? Here and a few other table rows below?

Comment thread standard/classes.md Outdated
Comment thread standard/classes.md Outdated
@RexJaeschke RexJaeschke added the Review: pending Proposal is available for review label Nov 7, 2025
@RexJaeschke RexJaeschke added this to the C# 9.0 milestone Nov 7, 2025
@BillWagner BillWagner changed the title Feature support for init accessors [Version 9.0] Feature support for init accessors Nov 7, 2025
@RexJaeschke RexJaeschke added the type: feature This issue describes a new feature label Nov 11, 2025
@jskeet jskeet self-assigned this Nov 27, 2025
@BillWagner
BillWagner force-pushed the v9-init-accessors branch from 1dd0bbe to 0a30078 Compare May 12, 2026 19:27
@BillWagner

Copy link
Copy Markdown
Member Author

An earlier version of this feature is already present on alpha-v9 from a prior meeting. Edits made to this PR since then are not yet on alpha-v9; they will land at the next propagation. If you need them on alpha-v9 sooner, please open a separate PR targeting alpha-v9.
Thanks!

@jskeet

jskeet commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Applied "meeting: discuss" so that we can all perform a first round of review before the next meeting.

@jskeet jskeet added the meeting: discuss This issue should be discussed at the next TC49-TG2 meeting label Jul 22, 2026
BillWagner added a commit that referenced this pull request Jul 24, 2026

@Nigel-Ecma Nigel-Ecma left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a partial review, stopped after reviewing classes.md.

I think changes are required.

Comment thread standard/classes.md
Comment on lines +1399 to +1400

A set accessor and init accessor have the same signature; however, that for the init accessor also has an implementation-defined form of annotation to distinguish it from a set accessor.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is description of a certain implementation, there appears to be no language level reason to require this. (Also there seems to be no CLR reason to do this; the CLR allows extra methods [accessors in C#] in a property. It is a particular C# implementation’s choice.)

It is also counter to the C# description of signature, which includes the name.

The language restriction is that there cannot be both a set and an init accessor.

Recommendation: remove

Suggested change
A set accessor and init accessor have the same signature; however, that for the init accessor also has an implementation-defined form of annotation to distinguish it from a set accessor.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could possibly add a note that init_P is not reserved. (But it doesn't need to be normative text.)

Comment thread standard/classes.md
```

Both signatures are reserved, even if the property is read-only or write-only.
Both signatures are reserved, even if the property has only one accessor.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We think this is okay - it's about the method signatures rather than the property accessor names being reserved.

Comment thread standard/classes.md
```

Both signatures are reserved, even if the indexer is read-only or write-only.
Both signatures are reserved, even if the indexer has only one accessor.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment for properties

Comment thread standard/classes.md
Both signatures are reserved, even if the indexer is read-only or write-only.
Both signatures are reserved, even if the indexer has only one accessor.

A set accessor and init accessor have the same signature; however, that for the init accessor also has an implementation-defined form of annotation to distinguish it from a set accessor.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment for properties

Comment thread standard/classes.md
The *accessor_declarations* consist of a *get_accessor_declaration*, a *set_accessor_declaration*, or both. Each accessor declaration consists of optional attributes, an optional *accessor_modifier*, the token `get` or `set`, followed by an *accessor_body*.
The *accessor_declarations* consist of either a *get_accessor_declaration*, optionally with a *set_accessor_declaration* or an *init_accessor_declaration*, or a *set_accessor_declaration* or *init_accessor_declaration* optionally with a *get_accessor_declaration*. Each accessor declaration consists of optional *attributes*, an optional *accessor_modifier*, the token `get`, `init`, or `set`, followed by an *accessor_body*.

For a ref-valued property the *ref_get_accessor_declaration* consists optional attributes, an optional *accessor_modifier*, the token `get`, followed by an *ref_accessor_body*.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing word? Also an -> a

Suggested change
For a ref-valued property the *ref_get_accessor_declaration* consists optional attributes, an optional *accessor_modifier*, the token `get`, followed by an *ref_accessor_body*.
For a ref-valued property the *ref_get_accessor_declaration* consists of optional attributes, an optional *accessor_modifier*, the token `get`, followed by a *ref_accessor_body*.

Comment thread standard/classes.md
Comment on lines +3845 to +3848
At the point an init accessor is invoked, the instance is known to be in the construction phase. Hence an init accessor may take the following actions in addition to what a set accessor can do:

1. Call other init accessors available through `this` or `base`
1. Assign `readonly` fields declared on the same type through `this`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this “instance”, it seems to pop up out of nowhere. Just sketching, hence not a suggestion block…

At the point a property’s init accessor is invoked, the property’s containing/owning type instance is known to be in the construction phase. Hence an init accessor may take the following actions in addition to what a set accessor can do:

  1. Set the value of read-init/init-only properties accessible through this or base
  2. Assign readonly fields declared on the same type through this

This also changes the wording “Call other init accessors” as code doesn’t directly call an accessor, unlike methods are directly called – the accessor is invoked as the consequence of some other action (assignment, ++, etc.). This might be a wider issue though!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 3781 isn't too much earlier, although it refers to "object" instead of "instance":

An instance property containing an init_accessor_declaration is considered settable during the construction phase of the object

But I don't object to us making it clearer, of course.

Comment thread standard/classes.md
{
Name = "Jared"
};
local.Name = "Jraed"; // Error

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentionally 2 errors (assignment & spelling) or not? If intentional it is fine.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My guess is that it's intentionally different but we might want to have a better example.

Comment thread standard/classes.md Outdated
Comment thread standard/classes.md
Comment on lines 4183 to +4184
- A set accessor corresponds to a method with a single value parameter of the property type, a void return type, and the same modifiers as the containing property.
- An init accessor corresponds to a method with a single value parameter of the property type, a `void` return type, and the same modifiers as the containing property.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following the spirit of the suggestion on 3494, why not:

Suggested change
- A set accessor corresponds to a method with a single value parameter of the property type, a void return type, and the same modifiers as the containing property.
- An init accessor corresponds to a method with a single value parameter of the property type, a `void` return type, and the same modifiers as the containing property.
- A set or init accessor corresponds to a method with a single value parameter of the property type, a `void` return type, and the same modifiers as the containing property.

I tending to think that init & set accessors should be described as differing only in when they can be invoked, and not be listing out the (identical) other features.

Comment thread standard/classes.md
>
> *end example*

When an init accessor appears in a virtual property, all overrides for it shall also be marked `init`. Likewise, it is not possible to override a set accessor with an init accessor.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it stated elsewhere that all overrides for set & get also need to be “marked” set & get respectively?

I’m unsure whether the second sentence is saying something useful, or is maybe coming from a particular implementation translating an init into a set with flag. If it is the latter then an earlier suggestion has been made to remove this implementation specific detail, and if that is accepted then the text here might need to be reworded as well. (I.e. if the Standard never surfaces the implementation specific detail that an init is a set why would anyone think you can override one with the other?)

Comment thread standard/classes.md
```

Both signatures are reserved, even if the property is read-only or write-only.
Both signatures are reserved, even if the property has only one accessor.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We think this is okay - it's about the method signatures rather than the property accessor names being reserved.

Comment thread standard/classes.md
Both signatures are reserved, even if the property is read-only or write-only.
Both signatures are reserved, even if the property has only one accessor.

A set accessor and init accessor have the same signature; however, that for the init accessor also has an implementation-defined form of annotation to distinguish it from a set accessor.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading this change again, "however, that for the init accessor" reads very oddly to me.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jskeet – It is odd & implementation specific, I recommended deleting it above

Comment thread standard/classes.md
- A property that includes both a get accessor and an init accessor is said to be a ***read-init property***. It is a compile-time error for a read-init property to be the target of an assignment except during the construction phase of an object (§init-accessors).
- A property that has only a get accessor is said to be a ***read-only property***. It is a compile-time error for a read-only property to be the target of an assignment.
- A property that has only a set accessor is said to be a ***write-only property***. Except as the target of an assignment, it is a compile-time error to reference a write-only property in an expression.
- A property that has only an init accessor is said to be an ***init-only property***. Except as the target of an assignment during the construction phase of an object (§init-accessors), it is a compile-time error to reference an init-only property in an expression.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't spotted before that we have these definitions. I wonder whether we should keep them and refer to them, but introduce a new term of "it's only got a get access but it's wriable-ref-returning, so you can still use it as the target of an assignment". (This has come up in a few places recently.)

Comment thread standard/classes.md
> <!-- Example: {template:"code-in-main-without-using", name:"AccessorsInit2", additionalFiles:["PointStructWithInit.cs"]} -->
>
> ```csharp
> var p = new Point() { X = 42, Y = 13 };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> var p = new Point() { X = 42, Y = 13 };
> var p = new Point { X = 42, Y = 13 };

(Just to be more idiomatic.)

Comment thread standard/classes.md

#### §init-accessors Init accessors

An instance property containing an *init_accessor_declaration* is considered settable during the construction phase of the object, except when in a local function or lambda. The ***construction phase of an object*** includes the following:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the "except when in a local function or lambda" part. When what is in a local function or lambda? Presumably the code that is invoking the init-accessor? An example might be beneficial.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jskeet – The restriction is (at least) that you cannot define a lambda in a constructor the body of which attempts to init the property. E.g.:

    internal class Program
    {
        static void Main(string[] args) { new Program(); }

        public Program()
        {
            P = 36; // OK
            (Func<int, int>)(x => Q = x) = 42; // Err: Init-only Q can only be assigned in an initializer
        }

        private int p;
        int P
        {
            get { return p; }
            init { p = value; }
        }

        private int q;
        int Q
        {
            get { return q; }
            init { q = value; }
        }
    }

Agree a (better than the above) sample would be helpful.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I think an example and a rewording would be helpful. Something about "can't be set via an implicit or explicit this reference captured by an anonymous function".

Comment thread standard/classes.md
>
> *end example*

The rules around when init accessors are settable extend across type hierarchies. If the member is accessible and the object is known to be in the construction phase, then the member is settable.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that include casting this to a derived type within a base type constructor?

internal class Base
{
    public Base()
    {
        // This isn't valid...
        if (this is Derived d)
        {
            d.X = 10;
        }
        // And neither is this...
        (Derived) this.X = 10;
    }
}

internal class Derived
{
    public int X { get; init; }

    public Derived()
    {
        X = 10;
    }
}

Maybe only refer to base types?

Add support for init accessors

Add support for init accessors

Add support for init accessors

Add support for init accessors

Add support for init accessors

Add support for init accessors

Add support for init accessors

fix md formatting

fix formatting, add xref links

add xref links

Co-authored-by: Copilot <[email protected]>
Copilot-Session: 21570b74-c6f5-4fc5-ab6d-7546918d6ddb
BillWagner and others added 6 commits October 13, 2026 14:17
Use different reasonable placeholders.

Co-authored-by: Copilot <[email protected]>
Copilot-Session: 21570b74-c6f5-4fc5-ab6d-7546918d6ddb
Co-authored-by: Copilot <[email protected]>
Copilot-Session: 21570b74-c6f5-4fc5-ab6d-7546918d6ddb
I made these tweaks after comparing the edits I researched 2+ years ago and what I found in this new feature branch.

Co-authored-by: Copilot <[email protected]>
Copilot-Session: 21570b74-c6f5-4fc5-ab6d-7546918d6ddb
Co-authored-by: Copilot <[email protected]>
Copilot-Session: 21570b74-c6f5-4fc5-ab6d-7546918d6ddb
Co-authored-by: Nigel-Ecma <[email protected]>
Co-authored-by: Copilot <[email protected]>
Copilot-Session: 21570b74-c6f5-4fc5-ab6d-7546918d6ddb
Co-authored-by: Copilot <[email protected]>
Copilot-Session: 21570b74-c6f5-4fc5-ab6d-7546918d6ddb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

meeting: discuss This issue should be discussed at the next TC49-TG2 meeting Review: pending Proposal is available for review type: feature This issue describes a new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants