Lesson 193's extension methods could only ever add methods. C# 14 finally lets you add extension properties and extension static members too — grouped under one shared receiver.
Lesson 193 ended with LINQ as the big example of extension methods — and quietly left something out. Every single one of those extension methods is, well, a method. What if you wanted someString.IsEmail to read as a property instead of someString.IsEmail()? What if you wanted a extension-style helper that's invoked on the type itself, like Enumerable.Empty<T>(), but grouped together with your instance-style extensions on the same type, instead of scattered across separate static methods? Before C# 14, the honest answer was: you can't, not cleanly. Extension methods, as a language feature, only ever let you add methods. C# 14 changes that.
C# 14 introduces a generalized notion of an extension member — a broader category than "extension method" that also includes extension properties and extension static members. Instead of writing one this Type value-prefixed method at a time (lesson 193's approach), you write an extension block: a small, dedicated section, still nested inside an ordinary static class, that declares the receiver type once, and then lets you list several extension members underneath it — methods, properties, and static members alike — all sharing that one declaration instead of repeating it on every single member.
The feature is built around a new extension(...) block declaration, written inside a static class, which names a receiver — the type (and, for instance members, the parameter representing "the value this extension applies to") that every member declared inside that block extends. Members declared inside the block can be instance-shaped (extension methods and extension properties, both usable with ordinary value.Member syntax) or static-shaped (extension static members, usable with TypeName.Member syntax), depending on how the block and its members are declared. Conceptually, this is best understood as C# 14 finishing a job lesson 193's mechanism started but couldn't complete: extension methods already let you add method-shaped behavior to a type you don't own; extension members generalize that same idea — "add behavior, callable with the syntax of a real member, to a type you don't own" — to properties and static members as well.
The general shape you should expect to see when reading C# 14 code is something like a static class containing an extension block that names a receiver, with ordinary-looking member declarations nested inside it — replacing the need to write this Type value on every individual method the way lesson 193 required. Confirm the precise keyword placement and receiver syntax against current official documentation before writing this in your own code, since this is one of the newest parts of the language.
// Illustrative shape only — confirm exact syntax against current C# 14 docs.
public static class StringExtensions
{
extension(string value)
{
// an extension PROPERTY — usable as: someString.IsValidEmail
public bool IsValidEmail => value.Contains('@') && !string.IsNullOrWhiteSpace(value);
// an extension METHOD — usable as: someString.Truncate(10)
public string Truncate(int maxLength) =>
value.Length <= maxLength ? value : value[..maxLength] + "...";
}
}
Lesson 193's extension methods are genuinely powerful, but their method-only limitation forces real compromises once you want something that's conceptually a property or a static factory, not an action:
value.IsValidEmail) than as a method call (value.IsValidEmail()). Before C# 14, you were stuck with the parentheses, even when the underlying computation had no side effects and was conceptually a property in every other sense.this Type value as the first parameter of every single method, even when a whole cluster of extension methods on a static class was really about the exact same receiver type — verbose, and a source of copy-paste inconsistency across a large extensions class.C# 14's extension blocks address both gaps at once: they extend the kinds of members you can add (methods, properties, static members — a genuinely broader mechanism than lesson 193's method-only version), and they let a whole group of related extension members share one receiver declaration instead of repeating it member by member. The underlying goal the language team has described for this feature is closing a real, long-standing gap: an extension method could always mimic an instance method's calling syntax, but had no equivalent for an instance property's calling syntax, or for a static member's calling syntax — until now.
Nothing about the fundamentals from lesson 193 has changed: extension members are still a compile-time-only, syntactic mechanism — they don't add real members to the underlying type, and a genuine real member on the type still wins over an extension member with a matching name, exactly as lesson 193 established for extension methods. C# 14 broadens what shapes of member you can add and how you group them, not the fundamental nature of what an "extension" is.
this Type valueextension(...) blockvalue.IsValid, no parenthesesRather than presenting a fully worked multi-member example whose exact syntax might not be precisely right, here's the mechanism described in plain terms, which you can rely on regardless of the exact final syntax details:
static class; nothing about that container has changed.this Type value on every method, you declare the receiver type (and a name for "the value being extended," for instance members) a single time, at the top of the block.value.SomeProperty, no parentheses.value.SomeMethod(args), exactly like lesson 193.TypeName.SomeMember, grouped in the same block as the instance-level members it conceptually belongs with.Here's a plausible, small example showing the concept: a Money-like extension over decimal, offering an extension property (a computed check) grouped together with an extension method (a computed transformation) — the exact pairing that lesson 193 could only express as two separate, unrelated-looking static methods.
// Illustrative — confirm exact syntax against current C# 14 / .NET 10 docs
// before relying on this shape in production code.
public static class MoneyExtensions
{
extension(decimal amount)
{
// Reads naturally as a property — no parentheses at the call site.
public bool IsNegative => amount < 0m;
// Reads naturally as a method — takes a parameter, performs work.
public decimal ApplyDiscount(decimal percentOff) =>
amount - (amount * percentOff / 100m);
}
}
// Usage — both members share the SAME receiver declaration above,
// instead of each repeating "this decimal amount" the way lesson 193 required:
decimal price = -5m;
bool negative = price.IsNegative; // true — property syntax, no ()
decimal discounted = (100m).ApplyDiscount(20m); // 80 — method syntax, unchanged from lesson 193
Code → Meaning → Result: Both IsNegative and ApplyDiscount extend decimal — a sealed BCL type you cannot modify, exactly the same constraint lesson 193 was built around. What's new here is that IsNegative reads as a true property at the call site, something lesson 193's method-only mechanism could never express, and that both members were declared under one shared receiver instead of each repeating it.
Think back to lesson 193's LINQ example: Enumerable is a large static class full of extension methods, all sharing the same conceptual receiver, IEnumerable<T>. Under the classic mechanism, every one of those dozens of methods independently repeats this IEnumerable<TSource> as its first parameter. A large, real-world extension library — a validation helpers library extending several BCL types, or an internal company library adding dozens of small conveniences onto a handful of shared types — has exactly this same shape: many members, clustered around a small number of receiver types. Extension blocks let a library author group all the members for one receiver type together, under one declaration, instead of scattering the receiver-parameter boilerplate across every single method — a maintainability and readability win as such a library grows, on top of unlocking property and static-member shapes that plain extension methods could never express at all.
Some things about this feature can be stated with confidence, because they follow directly from what extension methods have always been (lesson 193) and from how the C# team has consistently designed features like this:
string or decimal themselves. Whatever the compiler emits, it emits into your own static class, exactly as it always has for extension methods.this Type value-style extension methods; it adds a new, more general way to write extension members alongside them.What's genuinely less certain — and exactly where this lesson deliberately avoids overcommitting — is the precise keyword placement, the exact receiver-declaration syntax, whether static extension members are declared inside the same block as instance members or require a distinct block form, and any interactions with generics or nullable receiver types. Those are precisely the kinds of fine details worth confirming against Microsoft's official, current C# language reference for your specific compiler and SDK version before writing this pattern in real code.
It's tempting to treat a newer feature as obsoleting an older one, exactly as lesson 193 warned about default interface methods. Extension blocks are best understood as extension methods' natural evolution, not their replacement — a huge amount of existing C# code, and plenty of newly written code where a single simple method is all you need, will keep using the classic this Type value form indefinitely. Reach for an extension block specifically when you want a property or static-member shape, or when grouping several related members under one receiver genuinely improves readability.
An extension property, exactly like an extension method, cannot store any new state on the extended type itself — there's no hidden field being added to string or decimal. It's still purely a computed accessor, syntactic sugar over what is, underneath, an ordinary method-shaped operation on the receiver value. If you need genuinely new per-instance state associated with a value you don't own, extension members — old or new — were never the right tool for that; you'd need a wrapping type or an external, keyed lookup instead.
Treating any single example of C# 14 extension-block syntax — including the illustrative one in this lesson — as certainly, precisely correct, and pasting it directly into a real project without compiling it first.
For a feature this new, always compile a small test snippet against your actual installed SDK version and consult Microsoft's official, current C# language reference before relying on exact syntax in real code. The concept in this lesson is solid; the precise spelling is exactly the kind of detail worth a thirty-second verification.
Turning every extension member into a property purely because the new syntax makes it possible, even when the operation is expensive, has side effects, or conceptually represents "doing something" rather than "describing something" — exactly the same judgment call C# developers already make for ordinary properties versus methods on their own types.
Apply the same property-vs-method judgment here that you'd apply to a real type's own members: cheap, side-effect-free, conceptually descriptive computations read well as properties; anything else reads more honestly as a method.
this Type value on every one is genuinely hurting readability or inviting copy-paste drift.this Type value form remains perfectly fine — there's no obligation to reach for the newer, more general mechanism when the simpler one already says exactly what you mean.this Type value repeated on every method.extension(...) block declares a shared receiver once for a whole group of related members, instead of repeating this Type value on every individual method the way classic extension methods require.You've seen how C# 14 builds directly on lesson 193's extension methods. Let's confirm you understand the concept and its motivation — not any single line of syntax.
1. What capability could classic extension methods (lesson 193) never express, that C# 14's extension members introduce?
Correct: B
Why B is correct: This is the specific gap this lesson identifies — classic extension methods could only ever add method-shaped members. C# 14 generalizes the mechanism to also support property-shaped and static-shaped extension members.
Why A is incorrect: That's exactly what classic extension methods from lesson 193 already provided — it's the starting capability this lesson builds on, not the new one.
Why C is incorrect: Extending sealed types like string was already fully possible with classic extension methods, as shown throughout lesson 193 — this isn't new to C# 14.
Why D is incorrect: Namespace-scoped discovery via using was already how classic extension methods worked — this behavior is inherited, not introduced, by extension members.
Reinforcement: The genuinely new capability is the additional member shapes — properties and static members — not the underlying extension mechanism itself.
2. What problem does declaring a receiver once inside an extension(...) block solve, compared to classic extension methods?
Correct: B
Why B is correct: This is exactly the readability and repetition problem identified in "Why Does It Exist?" — a cluster of related extension members no longer needs to individually restate the same receiver parameter.
Why A is incorrect: There's no reasonable basis for assuming a runtime performance change from this purely compile-time, syntactic reorganization — nothing about execution speed is the motivation here.
Why C is incorrect: Extension members, old or new, never gain the ability to modify the actual underlying type's real fields — that fundamental limitation carries forward unchanged from lesson 193.
Why D is incorrect: Extension member discovery remains namespace-scoped via using, exactly as it was for classic extension methods — this aspect is unrelated to the receiver-block syntax.
Reinforcement: The receiver block is fundamentally a readability and repetition fix, not a change to what extension members can actually do to the underlying type.
3. A developer wants to write an extension member on decimal that checks whether the value is negative, and wants it to be callable as myAmount.IsNegative — with no parentheses. Under classic, lesson-193-only extension methods, was this achievable?
Correct: B
Why B is correct: This is precisely the gap this lesson exists to explain — lesson 193's mechanism was method-only, so a computed check like this could only ever be exposed as a method call with parentheses, not a true parentheses-free property, until C# 14's extension properties.
Why A is incorrect: Classic extension methods are, by definition, methods — C# has never allowed a method call to be written without parentheses regardless of the extension mechanism.
Why C is incorrect: This is exactly the capability C# 14's extension properties are described as adding in this lesson — the premise of the question is that it becomes achievable, not that it remains impossible.
Why D is incorrect: Extension methods have always worked on value types as well as reference types (lesson 193's own examples used decimal and string), so there's no such reference-type-only restriction.
Reinforcement: Parentheses-free, property-style syntax on a value you don't own is exactly the new capability this lesson attributes to C# 14's extension properties.
You've now traced extension-style behavior all the way from lesson 193's foundational this Type value mechanism through to C# 14's generalized extension members — understanding not just how each works, but exactly why the language grew this way.
dotnetmadeeasy.com — Learn C# and .NET, the right way.