# Field Backed Properties in .NET 10

Properties have always given C# developers a clean public API over internal state. For a simple value, an auto property keeps the implementation compact:

```csharp
public string DisplayName { get; set; } = string.Empty;
```

As soon as the property needs validation, normalisation or change notification, that compact declaration usually expands into a property plus a manually declared field. C# 14, which ships with .NET 10, fills the gap between those two forms with the `field` contextual keyword. `field` gives an accessor direct access to that property's compiler-synthesised backing field. We can add logic to `get`, `set` or `init` without declaring storage ourselves.

```csharp
public string DisplayName
{
    get;
    set => field = value.Trim();
} = string.Empty;
```

It is a small language feature, but it removes a surprisingly common piece of repetitive code while keeping the behaviour visible at the property boundary.

## What `field` changes

Before .NET 10, a property that rejected `null` needed an explicit field and implementations for both accessors:

```csharp
public sealed class UserProfile
{
    private string _displayName = string.Empty;
    public string DisplayName
    {
        get => _displayName;
        set => _displayName = value
            ?? throw new ArgumentNullException(nameof(value));
    }
}
```

With a field backed property, the compiler owns the field and we only write the behaviour we need:

```csharp
public sealed class UserProfile
{
    public string DisplayName
    {
        get;
        set => field = value
            ?? throw new ArgumentNullException(nameof(value));
    } = string.Empty;
}
```

The public property behaves in the same way. The difference is where the storage declaration comes from.

![](https://cdn.hashnode.com/uploads/covers/67c36038c69a4b7143c5fc49/d300d75d-cf57-46f4-9e6b-79fec67a9a90.png align="center")

Each field backed property has its own hidden field. `field` refers only to the field belonging to the property whose accessor is currently executing. It cannot accidentally refer to the backing field of another property. The feature works particularly well when one accessor needs logic and the other remains automatic:

```csharp
public decimal CreditLimit
{
    get;
    set => field = value is >= 0 and <= 100_000
        ? value
        : throw new ArgumentOutOfRangeException(
            nameof(value),
            "The credit limit must be between 0 and 100,000.");
}
```

Here, `get;` reads the generated field, while the custom setter validates before assigning to the same field.

## Normalising values at the boundary

Field backed properties are a good fit for small, deterministic transformations that should happen whenever a value enters an object. Trimming a reference, converting a code to a consistent case or clamping a numeric setting can now stay within the property declaration.

```csharp
public sealed class Submission
{
    public required string Reference
    {
        get;
        init => field = string.IsNullOrWhiteSpace(value)
            ? throw new ArgumentException(
                "A submission reference is required.",
                nameof(value))
            : value.Trim().ToUpperInvariant();
    }
}
```

The property remains `required`, and `init` still limits assignment to object initialisation or construction. The value is checked and normalised once as it enters the object:

```csharp
var submission = new Submission
{
    Reference = "  uw-2026-1042  "
};

Console.WriteLine(submission.Reference);
// UW-2026-1042
```

This is useful for value like properties, but I would keep larger workflows out of an accessor. A property assignment that performs I/O, updates several aggregates or has retry behaviour is difficult to reason about. In those cases, a method or factory still communicates the operation more clearly.

## Raising change notifications

Another common backing field pattern appears in UI and observable models. The setter compares the incoming value with the current value, updates the field, then raises `PropertyChanged`.

```csharp
using System.ComponentModel;
using System.Runtime.CompilerServices;

public sealed class SettingsViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;
    public string Theme
    {
        get;
        set
        {
            ArgumentException.ThrowIfNullOrWhiteSpace(value);
            if (field == value)
            {
                return;
            }

            field = value;
            OnPropertyChanged();
        }
    } = "System";

    private void OnPropertyChanged(
        [CallerMemberName] string? propertyName = null) =>
        PropertyChanged?.Invoke(
            this,
            new PropertyChangedEventArgs(propertyName));
}
```

Previously, `Theme` would also need a `_theme` field. The new form keeps all state-specific code together and removes the opportunity to read one field while accidentally writing another. The compiler generated field is still ordinary per-instance storage. `field` does not introduce observable behaviour on its own; the notification occurs only because the setter explicitly raises it.

## Custom getters and lazy values

`field` is available in getters as well. That makes it suitable for a small lazy cache where the value should be calculated on first access and then reused:

```csharp
public sealed class Report
{
    public string Content => field ??= BuildContent();
    private static string BuildContent()
    {
        Console.WriteLine("Building report content...");
        return "Generated report";
    }
}
```

The compiler supplies the storage needed by the `??=` expression. C# 14's nullable analysis understands this pattern: the hidden backing field can begin as `null`, while the public getter still returns a non-null `string`.

![](https://cdn.hashnode.com/uploads/covers/67c36038c69a4b7143c5fc49/2db61e0d-9f8b-49ca-945a-0be557adf0b2.png align="center")

This example is intentionally small. The property is not thread-safe simply because it uses `field`; concurrent callers could both run `BuildContent`. If initialisation must happen once across threads, use `Lazy<T>`, locking or another explicit concurrency mechanism.

## Initial values still work

A field backed property can have an

![]( align="center")

just like an auto property:

```csharp
public string Region
{
    get;
    set => field = value.Trim().ToUpperInvariant();
} = "IE";
```

The initialiser is applied directly to the generated backing field. It does not execute the custom setter. That distinction is useful, but it also means the initialiser should already satisfy the property's rules. In this example, `"IE"` is already in the normalised form the setter produces. Assignments made through the property, including assignments from a constructor, use its accessor logic. If construction needs to populate storage while deliberately bypassing that logic, an explicit backing field is the clearer option because `field` is only available inside the property's accessors.

## Using both accessor bodies

There is no requirement to leave one accessor automatic. Both can contain logic and share the generated field:

```csharp
public sealed class ApiOptions
{
    public Uri BaseAddress
    {
        get => field;
        set
        {
            ArgumentNullException.ThrowIfNull(value);
            field = value.IsAbsoluteUri
                ? value
                : throw new ArgumentException(
                    "The base address must be absolute.",
                    nameof(value));
        }
    } = new("https://api.example.com");
}
```

Writing `get => field;` is more verbose than `get;`, so I would normally use the automatic form unless the getter also needs behaviour. The important point is that `field` can be used by either or both accessors.

## `field` is contextual

`field` is a contextual keyword rather than a universally reserved word. Its special meaning applies within property accessors and property expression bodies. Existing code can therefore still contain a member named `field`, but an unqualified reference inside an accessor can become confusing:

```csharp
public sealed class LegacyExample
{
    private string field = "existing member";
    public string Value
    {
        get => field; 
        set => this.field = value; 
    }
}
```

Those two lines refer to different storage locations. `this.field` or `@field` can disambiguate the existing identifier, but renaming the member is usually easier for the next person reading the code.

There are two other scope details worth knowing:

*   `field` is available for properties, not indexers or event accessors.
    
*   `nameof(field)` is not supported because the generated field has no source-level name.
    

## When an explicit backing field is still useful

Field backed properties remove boilerplate; they do not replace every explicit field. I would retain a named field when code outside the property accessors must access the storage directly, when several properties intentionally share the same storage, or when a framework or reflection based tool depends on a particular field name.

An explicit field is also clearer when synchronisation spans more than a single accessor:

```csharp
public sealed class TemperatureCache
{
    private readonly object _sync = new();
    private decimal? _latest;
    public decimal Latest
    {
        get
        {
            lock (_sync)
            {
                return _latest ??= ReadTemperature();
            }
        }
    }

    public void Invalidate()
    {
        lock (_sync)
        {
            _latest = null;
        }
    }

    private static decimal ReadTemperature() => 18.5m;
}
```

`Invalidate` needs access to the same storage as the getter, so a named `_latest` field remains appropriate. There is also a migration detail around metadata. Replacing a named private field with compiler generated storage leaves the property's public API intact, but it changes the private field represented in metadata. Code that reaches into private fields through reflection, specialised serialisers or test helpers can observe that change. Such dependencies are brittle, but they still need checking before a broad automated refactor.

## Enabling field backed properties

Field backed properties are part of C# 14. A .NET 10 project selects C# 14 by default, so a normal SDK-style project needs no preview switch or explicit language version:

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>
```

I prefer allowing the target framework to choose its supported C# version rather than setting `LangVersion` to `latest`. It keeps local builds and CI aligned and avoids silently opting older target frameworks into unsupported language combinations.

## A sensible migration approach

The safest candidates are properties whose named backing field is used only by that property's accessors. The change is mechanical:

| Existing code | Field backed form |
| --- | --- |
| `get => _name;` | `get;` or `get => field;` |
| `_name = value;` inside the accessor | `field = value;` |
| `private string _name = "";` | Property initialiser `= "";` |
| Other methods access `_name` | Keep the explicit field |

After conversion, run the existing tests and search for reflection, serialisation mappings or configuration that refers to the old field by name. For observable objects, test that equal assignments still avoid duplicate events. For validated properties, test the boundary values and confirm the initialiser already follows the same rule.

They solve a narrow problem well. They let a property grow beyond automatic accessors without immediately introducing a separate field and duplicated getter code. Validation, normalisation, change notification and small lazy getters all become more compact, while the property remains the single place where its behaviour is defined. I would use them by default when the storage belongs exclusively to one property and all access can remain inside its accessors. Once other members need the storage, or its identity has significance outside the property, a conventional named field is still the more explicit design.

*   [The `field` contextual keyword — C# reference](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/field)
    
*   [What's new in C# 14](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-14)
    
*   [Properties — C# Programming Guide](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/properties)
    
*   [C# language versioning](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-versioning)
    
*   [`field` keyword feature specification](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/field-keyword.md)
