← Open in the full interactive course (progress tracking, search & more)

Back in lesson 194, this course gave you an early, cautious preview of extension members while C# 14 was still fresh. It's fully shipped now — time to cover it properly, with confirmed syntax and the static-member case that preview didn't have room for.

Lesson 193 taught you extension methods: a way to bolt method-shaped behavior onto a type you don't own, using ordinary instance.Method() call syntax underneath which the compiler quietly rewrites into a static method call. It's one of the most-used tricks in the whole language — LINQ itself is built entirely out of extension methods on IEnumerable<T>. But it had one hard limitation: everything you could add had to be a method. A computed yes/no check that reads naturally as a property, like someString.IsEmpty, could only ever be spelled someString.IsEmpty() — parentheses and all, whether they made sense to the reader or not.

In this lesson, you'll learn C# 14's extension block syntax — the generalized mechanism that lets a single block declare a receiver once and then add methods, properties, and even static members underneath it, closing the property-shaped and static-shaped gaps classic extension methods could never fill.

What Is It?

The Simple Explanation

An extension member is C# 14's broader category for everything lesson 193's extension methods used to be, plus two things they never could be: extension properties and extension static members. Instead of writing this Type value on every individual method, you open an extension(Type value) block inside an ordinary static class, declare the receiver once, and list every member that shares it underneath.

The Technical Definition

An extension(ReceiverType receiver) block, declared inside a static class, names a receiver parameter once for every instance-shaped member nested inside it — methods and properties alike, both callable at the use site with ordinary value.Member syntax. A second block form, extension(ReceiverType) with no named parameter, declares static extension members instead — callable with TypeName.Member syntax, with no instance involved at all. Both forms compile down to ordinary static methods on the containing class, exactly as classic extension methods always have — this is purely a richer, more expressive way to write the same underlying compile-time mechanism lesson 193 introduced.

Classic Extension Methods (Lesson 193)

🆕 C# 14 Extension Members

Why Does It Exist?

The Problem — Method-Only Extensions Forced Awkward Compromises

Two concrete gaps kept showing up once extension methods became a widely used tool:

The Solution — Generalize the Mechanism, Not Just the Method

C# 14 doesn't invent a new concept — it recognizes that "extension method" was really always a special case of a broader idea: adding a member, callable with ordinary member syntax, to a type you don't control. Extension blocks make that broader idea explicit, and let a whole cluster of related members — some methods, some properties, some static — share a single receiver declaration instead of each repeating it.

Big Picture

Extension Methods
Unchanged from lesson 193 — value.DoSomething(args), still fully supported
Extension Properties
New in C# 14 — value.IsSomething, no parentheses
Extension Static Members
New in C# 14 — TypeName.Something, no instance needed
One Shared Receiver
Declared once per extension(...) block, not per member

How It Works

FROM CLASSIC EXTENSION METHOD TO EXTENSION BLOCK
1. START WITH THE SAME static CLASS CONTAINER
2. OPEN AN extension(Type value) BLOCK FOR INSTANCE MEMBERS
3. DECLARE MEMBERS INSIDE, USING THEIR NATURAL SHAPE
4. OPEN A SECOND extension(Type) BLOCK FOR STATIC MEMBERS, IF NEEDED
5. THE CALLER SEES ORDINARY MEMBER SYNTAX — EXACTLY LIKE LESSON 193

Simple Example

public static class StringExtensions { // Instance-shaped members share ONE receiver declaration. extension(string value) { // An extension PROPERTY — usable as: someString.IsValidEmail public bool IsValidEmail => !string.IsNullOrWhiteSpace(value) && value.Contains('@'); // An extension METHOD — unchanged in spirit from lesson 193. public string Truncate(int maxLength) => value.Length <= maxLength ? value : value[..maxLength] + "..."; } // Static-shaped members go in their own block — no receiver instance. extension(string) { // An extension STATIC member — usable as: StringExtensions.Empty // (accessed via the extended type in practice: string.Empty-style usage) public static string Redacted => "***"; } } // Usage: string email = "user@example.com"; bool valid = email.IsValidEmail; // true — property syntax, no () string short_ = "Hello, world!".Truncate(5); // "Hello..."

Code → Meaning → Result: IsValidEmail and Truncate both extend string — a sealed BCL type you can't modify, exactly the constraint lesson 193 was built around. What's new is that IsValidEmail reads as a true property at the call site, and both members were declared under one shared receiver instead of each separately repeating it.

Real-World Example

Think back to lesson 193's LINQ example: Enumerable is a large static class, packed with extension methods that all share one conceptual receiver, IEnumerable<T>. A real-world validation-helpers library, or an internal company library adding a cluster of small conveniences onto a handful of shared types, has exactly this same shape: many related members, clustered around a small number of receiver types. Extension blocks let a library author group every member for one receiver together under one declaration, unlocking property and static-member shapes that plain extension methods could never express — a genuine readability and maintainability win as such a library grows past a handful of methods.

Analogy

A Building Directory, Grouped by Company Instead of Listed Room by Room

Classic extension methods are like a building directory that lists every single room individually, repeating the company name next to every entry: "Acme Corp — Room 201," "Acme Corp — Room 202," "Acme Corp — Room 203." It works, but it's repetitive, and it can't express "Acme Corp's front desk" — a thing that belongs to the company as a whole, not to any one room. An extension block is that same directory reorganized: "Acme Corp" is written once as a heading, with every room — and now, the front desk too — listed underneath it. Nothing about how visitors find a room has changed; the organization underneath it just stopped repeating itself and gained a place for things that aren't tied to one specific room at all.

Under the Hood

Common Confusion

1. "This replaces lesson 193's extension methods" — no, it generalizes them

It's tempting to treat a newer feature as obsoleting an older one. Extension blocks are best understood as extension methods' natural evolution, not their replacement — a single simple method with no siblings on the same receiver is still perfectly well served by the classic this Type value form. 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.

2. "Extension static members are called through an instance" — no, through the type name

A member declared in a receiver-less extension(Type) block is a static member — it's invoked as TypeName.Member, exactly like any other static member you've written since lesson 018, not through an instance value the way extension properties and methods are. Mixing this up is the most common early mistake with the feature.

Common Mistakes

Mistake 1 — Reaching for an extension property when a method is genuinely clearer

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."

Apply the same property-vs-method judgment you already use for a real type's own members: cheap, side-effect-free, descriptive computations read well as properties; anything else reads more honestly as a method.

Mistake 2 — Rewriting an entire existing extensions class just to use the new syntax

Going through a working, well-tested extensions class and converting every method into an extension block purely because it's now possible, introducing churn with no functional benefit.

Use extension blocks for new code, and for existing code specifically when adding a property or static member the classic form couldn't express — leave working method-only extension classes as they are otherwise.

When Should I Use It?

Mental Model

Lesson 193: extension methods only — this Type value repeated on every method.
C# 14: extension methods, properties, and static members — the receiver declared once, shared by a whole group.
Same foundation both ways: compile-time-only sugar, a real member always wins, nothing is truly added to the extended type.

Key Takeaway


Check Your Understanding

You've seen how C# 14 builds directly on lesson 193's extension methods. Let's confirm the concept and its motivation landed.

1. What capability could classic extension methods (lesson 193) never express, that C# 14's extension members introduce?

Show answer

Correct: B

Why B is correct: This is the specific gap this lesson identifies — classic extension methods could only 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 — the starting capability this lesson builds on, not the new one.

Why C is incorrect: Extending sealed types like string was already possible with classic extension methods, as shown throughout lesson 193 — not new to C# 14.

Why D is incorrect: Namespace-scoped discovery via using was already how classic extension methods worked — 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. Given extension(string value) { public bool IsShort => value.Length < 5; }, how would a caller invoke IsShort on a variable named name?

Show answer

Correct: B

Why B is correct: IsShort is declared as a property inside an instance-receiver extension(string value) block, so it's called with ordinary property syntax — no parentheses — exactly like IsValidEmail in the Simple Example.

Why A is incorrect: Parentheses are for methods; a property-shaped extension member specifically avoids them, which is the entire point of extension properties existing.

Why C is incorrect: Static call syntax applies to members declared in a receiver-less extension(Type) block, not to instance-shaped properties declared with a named receiver parameter.

Why D is incorrect: It's directly readable as name.IsShort, exactly like any other property — no intermediate assignment is required.

Reinforcement: Whether a member needs parentheses at the call site depends entirely on whether it was declared as a method or a property inside the extension block.

3. What happens if a real, genuine property named IsValidEmail is later added directly to the string-like type being extended, with the same name as an existing extension property?

Show answer

Correct: B

Why B is correct: "Under the Hood" states this precisely — this foundational rule from lesson 193 carries forward unchanged for extension members. A genuine member on the actual type always takes priority.

Why A is incorrect: This situation doesn't raise a compile error — it resolves silently in favor of the real member, exactly as classic extension method resolution always has.

Why C is incorrect: This gets the priority exactly backwards — extension members are always the fallback, never able to override a genuine member.

Why D is incorrect: Only one member resolves at the call site; there's no mechanism for combining an extension member's result with a real member's result.

Reinforcement: Extension members, old or new, are always a fallback mechanism — a real member on the type always wins.

You've now traced extension-style behavior all the way from lesson 193's foundational this Type value mechanism through to C# 14's fully shipped, generalized extension members. Lesson 325 picks up the next stop on the Part XII tour — field-backed properties.


dotnetmadeeasy.com — Learn C# and .NET, the right way.