C# 14 extension members: the shadowing bug the compiler won't warn you about
C# 14's extension member syntax now covers properties and static members, not just methods, yet a type's own member still silently wins over an identically named extension member. The author reproduces this against the .NET 10 SDK with eight tests: the build reports zero warnings and zero errors while every call site quietly switches to new logic.
Key points
- A real member always beats an identically named extension member, silently, with no warning or error on the .NET 10 SDK.
- Two extension blocks declaring the same name on one receiver fail loudly with CS9339, so that case is caught at compile time.
- Extension properties cannot appear in expression trees: an EF Core `Where` predicate fails with CS9296 before it ever ships.
- Static extension calls are indistinguishable from real statics, so a future NuGet version adding the same name silently takes over.
- Defence in depth means a value-asserting test plus diff review that looks for member names an extension block already claims.
- Classic this-parameter extension methods and extension blocks compile to identical IL, so migration can be incremental.
In C# 14, an `extension(Order order)` block can declare instance and static methods, properties and operators against a receiver type. In the reproduced scenario a team adds an `IsEligibleForFreeShipping` extension property to a shared `Order` record. Weeks later another engineer promotes the same rule to a real member on `Order` with slightly different logic; the PR merges, CI stays green and the build reports zero warnings, yet the extension member is now permanently unreachable.
The precedence rule itself is not new: a type's own member always beats an identically named extension, with no diagnostic. What changed in C# 14 is the surface it covers, since properties and static members read exactly like real ones at the call site, so `Order.Empty(...)` gives no hint where it resolves. Collisions between two extension blocks fail with CS9339, and an extension property inside an expression tree fails with CS9296.
Real-world use
- A domain record shared across three services that nobody wants to modify for a shipping promotion rule.
- Layer-specific mapping helpers for a sealed NuGet or BCL type your team does not own.
- Computed fields used in EF Core predicates, written as real instance members to stay clear of CS9296.
- A gradual, file-by-file migration from this-parameter extension methods to extension blocks that compile to the same IL.
Why it matters
A rename that leaves the build clean can swap working logic for different logic, so extension properties and static members are a silent regression risk whenever their names could later be claimed by the type itself.