---
name: dotnet10-features
description: 'Reference for the .NET 10 / C# 14 features that are relevant to the ReactiveProperty repository. Use this when adopting or evaluating .NET 10 / C# 14 language and Blazor features in this codebase: modernizing INotifyPropertyChanged property accessors, refactoring the many `Reactive.Bindings` extension methods, raising LangVersion or adding a net10.0 target framework, or working on ReactiveProperty.Blazor / Blazor samples. Triggers include mentions of ".NET 10", "C# 14", "field keyword", "extension members", "net10.0", "LangVersion", or Blazor net10 work in this repo.'
---
# .NET 10 / C# 14 features relevant to ReactiveProperty
This skill curates the **.NET 10** (and its bundled **C# 14**) features that actually
matter for this repository, and maps each one to concrete ReactiveProperty scenarios.
It is intentionally scoped: it skips .NET 10 features with no bearing on a reactive MVVM
library.
## Repository context (read this first)
Target frameworks and language version are **not uniform** across the solution, so a C# 14
feature may be unavailable in a given project:
| Project | Target framework(s) | C# 14 available? |
| --- | --- | --- |
| `ReactiveProperty.Core` | `netstandard2.0;net8.0;net9.0;net472` | Only if `LangVersion` is raised; runtime-dependent features stay off on `netstandard2.0`/`net472` |
| `ReactiveProperty` (main, `ReactiveProperty.NETStandard`) | `netstandard2.0;net8.0;net9.0;net472` | Same as above |
| `ReactiveProperty.Platform.WPF` | `net8.0-windows;net9.0-windows;net472` | Same as above |
| `ReactiveProperty.Platform.UWP` | (UWP TFMs) | Same as above |
| `ReactiveProperty.Platform.Blazor` | `net8.0;net9.0;net10.0` | Yes on the `net10.0` leg |
| `Samples/**`, Blazor samples | include `net10.0` | Yes |
Key facts:
- `Source/Directory.Build.props` currently pins **`12.0`**, so the
library compiles as **C# 12** today. Adopting any C# 14 feature in the library requires
raising `LangVersion` (to `14` / `latest` / `preview`).
- `net10.0` is currently used only by **`ReactiveProperty.Platform.Blazor`** and the
**samples**. The core/main/WPF/UWP libraries do **not** target `net10.0`.
- The library is **multi-targeted down to `netstandard2.0` and `net472`**. C# 14 *language*
features that the compiler can lower (e.g. `field`, extension members, null-conditional
assignment, partial members) work across all TFMs once `LangVersion` is raised, but
features that depend on the **.NET 10 runtime/BCL** (e.g. first-class `Span` conversions
that rely on new BCL APIs) will not light up on the older TFMs. Guard those with
`#if NET10_0_OR_GREATER` when needed.
## How to enable C# 14 in a project here
```xml
14.0
```
For runtime/BCL-gated features, multi-target and branch with the framework constant:
```csharp
#if NET10_0_OR_GREATER
// .NET 10-only path
#else
// fallback for net8.0 / net9.0 / netstandard2.0 / net472
#endif
```
---
## C# 14 features, ranked by relevance to this repo
### 1. The `field` keyword — HIGH relevance
`field` exposes the compiler-synthesized backing field inside a property accessor, so you can
add validation/notification without declaring an explicit field. ReactiveProperty exists to
serve MVVM/`INotifyPropertyChanged`, and both the library internals and consumer ViewModels
are full of hand-written backing fields.
Classic INPC property:
```csharp
private string _name = "";
public string Name
{
get => _name;
set
{
if (_name == value) return;
_name = value;
PropertyChanged?.Invoke(this, new(nameof(Name)));
}
}
```
With `field`:
```csharp
public string Name
{
get;
set
{
if (field == value) return;
field = value;
PropertyChanged?.Invoke(this, new(nameof(Name)));
}
}
```
Notes for this repo:
- Watch for the naming collision: identifiers literally named `field` must be disambiguated
with `@field` or `this.field`.
- Works on all TFMs once `LangVersion >= 14`; this is the lowest-risk C# 14 adoption here.
### 2. Extension members — HIGH relevance
ReactiveProperty's public surface is heavily built on **extension methods** in the
`Reactive.Bindings` namespace (`ToReactiveProperty`, `ToReadOnlyReactivePropertySlim`,
`ObserveProperty`, `CombineLatest` helpers, etc.). C# 14 adds an `extension` block syntax that
also enables **extension properties**, **static extension members**, and **extension
operators** — grouped by receiver instead of repeating `this T source` on every method.
```csharp
public static class ObservableExtensions
{
extension(IObservable source)
{
// extension method (same call site as today)
public ReactiveProperty ToReactiveProperty(T initialValue = default!)
=> new ReactiveProperty(source, initialValue);
// NEW: extension property
public IObservable Shared => source.Publish().RefCount();
}
}
```
Notes for this repo:
- Lets related operators on `IObservable` / `IReactiveProperty` be grouped in one
`extension(...)` block, improving discoverability and enabling property-style helpers.
- This is a **language** feature (compiler-lowered), so it can apply across all TFMs once
`LangVersion` is raised — but it is an API-shape decision; keep existing extension method
signatures for source/binary compatibility and only add new members.
### 3. Null-conditional assignment (`?.`/`?[]` on the left side) — MEDIUM relevance
You can now assign through `?.`/`?[]`:
```csharp
// before
if (viewModel is not null) viewModel.SelectedItem = item;
// C# 14
viewModel?.SelectedItem = item;
```
Useful in ViewModel/event-handler glue and in sample apps. Increment/decrement
(`++`/`--`) are not allowed on the conditional left side; compound assignments (`+=`, etc.)
are.
### 4. First-class `Span` / `ReadOnlySpan` conversions — MEDIUM (performance)
C# 14 adds implicit conversions among `Span`, `ReadOnlySpan`, and `T[]`, improving
overload resolution and generic inference for span-based, allocation-free code paths. Relevant
only in hot paths (e.g. buffer/array handling inside operators).
Caveat: the most useful conversions rely on **.NET 10 BCL** support, so gate span-optimized
paths with `#if NET10_0_OR_GREATER` and keep an allocating fallback for `net8.0`/`net9.0`/
`netstandard2.0`/`net472`.
### 5. Simple lambda parameters with modifiers — MEDIUM/LOW relevance
Modifiers (`ref`, `in`, `out`, `scoped`, `ref readonly`) can now be applied to lambda
parameters **without** restating the type:
```csharp
delegate bool TryParse(string text, out T result);
TryParse parse = (text, out result) => int.TryParse(text, out result);
```
Handy for Rx/LINQ-style lambdas and `TryParse`-shaped converters in samples. `params` still
requires an explicit type.
### 6. Partial events and constructors — MEDIUM relevance
Instance constructors and events can now be `partial` (one defining + one implementing
declaration). This is primarily a **source-generator** enabler. If any INPC/event plumbing in
this repo (or a consumer toolkit pattern) is generated, partial events let a generator provide
`add`/`remove` accessors while the field-like event is declared by hand.
### 7. `nameof` with unbound generic types — LOW relevance
`nameof(ReactiveProperty<>)` now evaluates to `"ReactiveProperty"`. Minor convenience for
diagnostics, logging, and tests that reference open generic types.
### 8. User-defined compound assignment operators — LOW relevance
You can define `+=`, `-=`, etc. directly (instead of relying on the binary operator plus an
assignment). Niche for this library; mentioned for completeness.
---
## ASP.NET Core / Blazor 10 (for `ReactiveProperty.Platform.Blazor` and Blazor samples)
`ReactiveProperty.Platform.Blazor` targets `net10.0` and centers on **EditForm validation**
(`ReactivePropertiesValidator`, `EditContext.EnableReactivePropertiesValidation()`) plus
command helpers. The most relevant .NET 10 Blazor changes:
- **Improved form validation** — new opt-in validation (`builder.Services.AddValidation()` +
`[ValidatableType]`) that validates nested objects and collection items. This overlaps with
ReactiveProperty's `EditContext` validation integration; be aware of how the two interact
when both are enabled on the same `EditForm`, and document the recommended combination.
- **`OwningComponentBase` now implements `IAsyncDisposable`** — relevant to the disposal
pattern in `ReactivePropertiesValidator` (currently `IDisposable` wrapping a
`SingleAssignmentDisposable`). Async disposal can be useful where reactive subscriptions own
async resources.
- **Declarative state persistence (`[PersistentState]`)** — public properties marked
`[PersistentState]` are auto-persisted via `PersistentComponentState` during prerendering,
removing the manual `RegisterOnPersisting` boilerplate. Useful for ViewModel-held state in
prerendered Blazor Web App samples.
- **Not Found handling** (`Router` `NotFoundPage` parameter and
`NavigationManager`-based Not Found responses) and **Blazor script served as a static web
asset** — general improvements worth knowing when updating the Blazor samples to `net10.0`.
When bumping the Blazor sample/template projects, also review the version-conditioned
`Microsoft.AspNetCore.Components.*` package versions in `Directory.Packages.props`
(`10.0.0` entries are gated on `'$(TargetFramework)' == 'net10.0'`).
---
## Adoption caveats (summary)
- The core libraries are **C# 12 today** (`LangVersion 12.0`) and do **not** target `net10.0`.
Raising `LangVersion` is required before any C# 14 syntax compiles.
- Multi-targeting to `netstandard2.0`/`net472` means **runtime/BCL-gated** features must be
`#if NET10_0_OR_GREATER`-guarded; language-only features are fine across TFMs.
- This is a published NuGet library — preserve existing public/extension API signatures for
source and binary compatibility; prefer **adding** members over changing them.
## References (official docs)
- What's new in C# 14:
- Extension members tutorial:
- The `field` keyword:
- Null-conditional assignment:
- First-class spans:
- What's new in .NET 10:
- What's new in ASP.NET Core in .NET 10: