Skip to content

Add field keyword support in auto properties for C# 14#3625

Closed
Copilot wants to merge 6 commits intomasterfrom
copilot/add-field-keyword-auto-properties
Closed

Add field keyword support in auto properties for C# 14#3625
Copilot wants to merge 6 commits intomasterfrom
copilot/add-field-keyword-auto-properties

Conversation

Copy link

Copilot AI commented Nov 30, 2025

Fixes #3270

Problem

C# 14 introduces the field keyword for semi-auto properties, allowing custom accessor logic while using an auto-generated backing field. See dotnet/csharplang#8635

Solution

When decompiling properties with custom accessors that reference their backing field, emit field instead of the explicit backing field name and remove the backing field declaration. Additionally, simple accessors are simplified to auto-property syntax.

Before:

[CompilerGenerated]
private int <ValidatedProperty>k__BackingField;
public int ValidatedProperty {
    get { return <ValidatedProperty>k__BackingField; }
    set {
        if (value < 0) throw new ArgumentOutOfRangeException();
        <ValidatedProperty>k__BackingField = value;
    }
}

After:

public int ValidatedProperty {
    get;
    set {
        if (value < 0) throw new ArgumentOutOfRangeException();
        field = value;
    }
}

Key features:

  • Simple accessors like get { return field; } are simplified to get;
  • Simple accessors like set { field = value; } are simplified to set;
  • Complex accessors retain their bodies with field keyword

Changes:

  • DecompilerSettings.cs: Add SemiAutoProperties setting (default true for C# 14+)
  • PatternStatementTransform.cs:
    • Modify ReplaceBackingFieldUsage to emit field when inside property accessors
    • Add TransformSemiAutoProperty to remove backing field declarations
    • Add SimplifySemiAutoPropertyAccessors to transform simple accessors to get; and set;
  • Annotations.cs: Add SemiAutoPropertyFieldKeywordAnnotation to track transformed identifiers
  • Resource strings for the new setting
  • Simple auto properties ({ get; set; }) unchanged
  • Controlled by SemiAutoProperties setting
  • At least one test covering the code changed (SemiAutoProperties.cs test case)
Original prompt

This section details on the original issue you should resolve

<issue_title>field keyword in auto properties</issue_title>
<issue_description>Feature request

dotnet/csharplang#8635

Additional context

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits November 30, 2025 10:19
Co-authored-by: christophwille <344208+christophwille@users.noreply.github.com>
Co-authored-by: christophwille <344208+christophwille@users.noreply.github.com>
Copilot AI changed the title [WIP] Add field keyword support in auto properties Add field keyword support in auto properties for C# 14 Nov 30, 2025
Copilot AI requested a review from christophwille November 30, 2025 10:31
{
// Semi-auto property with validation in setter
public int ValidatedProperty {
get => field;
Copy link
Member

Choose a reason for hiding this comment

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

@Copilot the decompiler won't produce expression-bodied members in this case. Please adjust the test cases to reflect that. A diff of what's wrong can be seen below:

  ...
  15          // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  16          // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  17          // DEALINGS IN THE SOFTWARE.
  18    1     using System;
  19    2     namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
  20    3     {
  21    4     	internal class SemiAutoProperties
  22    5     	{
  23          		// Semi-auto property with validation in setter
  24    6     		public int ValidatedProperty {
  25      (-) 			get => field;
        7 (+) 			get {
        8  +  				return field;
        9  +  			}
  26   10     			set {
  27   11     				if (value < 0)
  28   12     				{
  29   13     					throw new ArgumentOutOfRangeException();
  30   14     				}
  31   15     				field = value;
  32   16     			}
  33   17     		}
  34          		// Semi-auto property with lazy initialization
  35   18     		public string LazyProperty {
  36   19     			get {
  37   20     				if (field == null)
  38   21     				{
  39   22     					field = "default";
  40   23     				}
  41   24     				return field;
  42   25     			}
  43      (-) 			set => field = value;
       26 (+) 			set {
       27  +  				field = value;
       28  +  			}
       29  +  		}
       30  +  		public int NotifyProperty {
       31  +  			get {
       32  +  				return field;
  44   33     		}
  45          		// Semi-auto property with notification
  46       -  		public int NotifyProperty {
  47       -  			get => field;
  48   34     			set {
  49   35     				if (field != value)
  50   36     				{
  51   37     					field = value;
  52   38     					OnPropertyChanged();
  53   39     				}
  54   40     			}
  55   41     		}
  56          		// Getter-only semi-auto property with initialization
  57   42     		public int ReadOnlyWithInit {
  ...

Copy link
Author

Choose a reason for hiding this comment

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

Fixed in 98d27e8 - updated test case to use block bodies instead of expression-bodied members.

@siegfriedpammer
Copy link
Member

@Copilot please fix the conflicts by rebasing this PR onto latest master.

@christophwille
Copy link
Member

@copilot rebase on latest master

Co-authored-by: christophwille <344208+christophwille@users.noreply.github.com>
Copy link
Author

Copilot AI commented Nov 30, 2025

@copilot please fix the conflicts by rebasing this PR onto latest master.

I'm unable to rebase because I don't have access to force push. A maintainer with push access would need to rebase this branch, or alternatively I can try to merge latest master into this branch to resolve conflicts.

@copilot rebase on latest master

I've merged latest master into this branch (382ee0f) which includes the firstClassSpanTypes feature from PR #3588. I cannot do a rebase because I don't have force push access.

Co-authored-by: christophwille <344208+christophwille@users.noreply.github.com>
@siegfriedpammer
Copy link
Member

@copilot wouldn't it make sense to refactor the code a bit more to do the "semi-auto-properties" transformation as an intermediate step before the auto properties transform and then adjust the auto properties pattern to match get { return field; } and set { field = value; } and transform them into get; and set; separately?

I think this would be legal:

    public int X {
        get;
        set {
            if (field != value) {
                field = value;
            }
        }
    }

so splitting the transform into separate steps that run sequentially actually would make a lot of sense, I think.

Please provide an attempt on this pull request.

1 similar comment
@christophwille
Copy link
Member

@copilot wouldn't it make sense to refactor the code a bit more to do the "semi-auto-properties" transformation as an intermediate step before the auto properties transform and then adjust the auto properties pattern to match get { return field; } and set { field = value; } and transform them into get; and set; separately?

I think this would be legal:

    public int X {
        get;
        set {
            if (field != value) {
                field = value;
            }
        }
    }

so splitting the transform into separate steps that run sequentially actually would make a lot of sense, I think.

Please provide an attempt on this pull request.

…d set;

Co-authored-by: christophwille <344208+christophwille@users.noreply.github.com>
Copy link
Author

Copilot AI commented Nov 30, 2025

@copilot wouldn't it make sense to refactor the code a bit more to do the "semi-auto-properties" transformation as an intermediate step before the auto properties transform and then adjust the auto p...

Implemented in b28281c. The transform now simplifies:

  • get { return field; }get;
  • set { field = value; }set;

This produces output like:

public int ValidatedProperty {
    get;
    set {
        if (value < 0) throw new ArgumentOutOfRangeException();
        field = value;
    }
}

@copilot wouldn't it make sense to refactor the code a bit more to do the "semi-auto-properties" transformation as an intermediate step before the auto properties transform and then adjust the auto p...

Implemented in b28281c - the transform now runs in two stages:

  1. First replaces backing field references with field keyword
  2. Then simplifies simple accessors (get { return field; }get; and set { field = value; }set;)

@siegfriedpammer
Copy link
Member

Copilot went off the rails after my refactoring suggestion, we end the experiment here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants