Introduction

In earlier versions of C#, when you wanted to add logic (validation, trimming, constraints) inside property accessors, you had to declare a private backing field manually. That leads to verbose boilerplate:

private string _name;
public string Name
{
    get => _name;
    set
    {
        if (value is null) throw new ArgumentNullException(nameof(value));
        _name = value;
    }
}

On the other hand, auto-implemented properties (e.g. public string Name { get; set; }) simplify things by having the compiler generate a hidden private field behind the scenes. But until now, you couldn’t inject logic (validation, normalization) into the setter or getter without giving up that simplicity and explicitly declaring a backing field.

C# 14 (as of preview or post-preview) introduces a contextual keyword field (first available as a preview in C# 13) that lets you refer to that compiler-generated backing field inside your property accessors. In effect, you can have the sugar of auto-properties plus custom logic in accessors.

Thus “field-backed properties” is shorthand for “auto-implemented properties that allow accessor logic via the field keyword.”

What exactly is the field contextual keyword?

Because of that, you get the benefit: automatic field generation + ability to add logic in accessors — without manually declaring a backing field.

Syntax forms & examples

Basic usage in setter

You can leave get; auto-implemented, and define logic in set:

public string Message
{
    get;
    set => field = value ?? throw new ArgumentNullException(nameof(value));
}

Here:

Full definition (get + set)

You can also provide logic both in getters and setters:

public int Age
{
    get
    {
        // maybe protect against not-initialized state or default
        return field;
    }
    set
    {
        if (value < 0) throw new ArgumentOutOfRangeException(nameof(value));
        field = value;
    }
}

Or using expression-bodied style:

public double Hours
{
    get => field;
    set => field = value >= 0
        ? value
        : throw new ArgumentOutOfRangeException(nameof(value), "Must be non-negative");
}

This makes the property concise, yet expressive.

Combined: auto get, custom set, or vice versa

You are free to mix. For instance, only setter logic:

public string Name
{
    get;
    set => field = value.Trim();
}

If you have no logic to apply in the setter, you could also write a custom get only and leave set; auto, though typically logic goes in set.

Backward compatibility & versioning

<PropertyGroup>
  <LangVersion>preview</LangVersion>
</PropertyGroup>

Edge cases, pitfalls & naming conflicts

Name conflict: if you already have a member named field

Because field is now meaningful inside accessor bodies, having a class-level field or local variable named field can lead to ambiguity or conflicts. For example:

private int field;
public int X
{
    get;
    set => field = value;  // ambiguous: which `field`?
}

In this case, inside the property accessor field refers to the compiler-generated backing field, not your declared field field. The compiler will warn about the naming conflict.

To disambiguate, you can use:

But generally, it’s wise to rename your class-level field to avoid the conflict.

Limitations: no access outside the property

You cannot use field outside of property accessors to bypass logic or do direct assignments. If you try, field is not in that scope. That ensures encapsulation.

Complex getters/logic & read-only patterns

If your getter logic becomes more involved — e.g. lazy initialization, computed values, or side effects — field alone might not suffice. You might still prefer explicit backing fields. The field approach is best for short validation or transformation. Many advanced patterns might still require manual backing fields.

Nullable reference types & default values

When using field in properties with nullable reference types, be careful about initialization and nullability. If you rely on default values or null, ensure safeguards (e.g. field ??= ...) or proper null checks.

Partial properties & source-generated code

Because field lets you avoid declaring explicit backing fields in source-generated code, it plays nicely with partial types or code generation, where you want to embed property logic in generated code while hiding the actual field.

Comparison: classic manual backing vs field vs pure auto-properties

ApproachExplicit backing fieldAccessor logicBoilerplateFlexibilityRisks
Manual backing fieldHighFullMore code, name duplication
Pure auto-propertyMinimalNoneCan’t validate or transform
Auto + field keyword❌ (implicit)LowModerateNaming conflict, limited to simple logic

When to use (and when not to use) field-backed properties

Use field approach when:

Avoid or prefer manual backing when:

Real-world examples & patterns

Example 1: non-nullable property with trimming

public string Title
{
    get;
    set => field = (value?.Trim() ?? throw new ArgumentNullException(nameof(value)));
}

Now, any assignment to the Title gets trimmed and checked.

Example 2: range-checked integer

public int Count
{
    get => field;
    set => field = (value >= 0)
        ? value
        : throw new ArgumentOutOfRangeException(nameof(value), "Count cannot be negative");
}

Example 3: defaulting fallback

public string Description
{
    get;
    set => field = string.IsNullOrEmpty(value) ? "<none>" : value;
}

Example 4: init + validation

You might combine with init or get; init => ... (in newer C# versions) to make immutable with logic.

public int Score
{
    get;
    init => field = value switch
    {
        < 0 => throw new ArgumentOutOfRangeException(nameof(value)),
        > 100 => 100,
        _ => value
    };
}

Example 5: Partial type/generator

In generated code, you can define:

partial class MyModel
{
    public int Age
    {
        get;
        set => field = Math.Clamp(value, 0, 120);
    }
}

A user-defined partial might extend it elsewhere without needing to know about the backing field.

Integration in your project & enabling preview features

To use field (if it’s still behind preview in your SDK), set your .csproj:

<PropertyGroup>
  <LangVersion>preview</LangVersion>
</PropertyGroup>

Make sure your SDK and compiler support C# 14 / .NET versions that include the feature. Some blog posts and documentation mention enabling it via preview language versions.

Use compiler warnings and analyzer support to check for naming conflicts (e.g. classes using field as a variable name).

Summary & takeaways