Modern C# · Lesson 37

C# 14 extension blocks and properties

Compare an ordinary helper with a real extension-property project for .NET 10.

External .NET 10 SDK / stable C# 14. This project does not run in SharpRune's .NET 9 playground.

Read or download the complete SDK project, then follow its setup on your computer. The separate C# companion practice below runs in SharpRune’s .NET 9 console environment.

The concept and environment

C# 14 extension blocks group extension members around a receiver type and support extension properties as well as methods. The actual syntax is provided in the external .NET 10 project, where an extension block on int declares IsBatch and Double. SharpRune's .NET 9 companion intentionally uses ordinary static helpers with the same small rules. It is a logic comparison, not a compiler-feature emulator. Read the environment labels before copying code: a successful helper execution here does not mean the browser compiler accepts extension blocks or extension-property syntax.

Walk through the portable program

The portable helper considers a quantity a batch when it is at least three. Calling it with two therefore prints false, and calling it with three prints true. Doubling four in checked integer arithmetic returns eight. Output is two:False, three:True, and double:8. These are independently predictable domain results, with the threshold boundary made visible. The companion deliberately spells calls as BatchRules methods, making the older syntax explicit rather than displaying fake extension properties while secretly translating them into a different program behind the reader's back.

Compile the genuine external feature

In the external project, QuantityExtensions is an ordinary static container with an extension(int quantity) block. Its IsBatch member is used as a property on the receiver, while Double is invoked as a method. The project targets net10.0 and explicitly selects stable language version 14.0, so compiling it exercises the genuine feature. Its expected output matches the helper's results, but the compiler binding being tested is different. Save every supplied file in the same project directory, then run the documented project command with a suitable .NET 10 SDK.

Pitfalls and limits

Extension members do not modify the original int type or add per-instance storage to it. They provide additional statically resolved operations through the available extension declarations. Existing applicable instance members and name resolution still matter, and a missing namespace can make an extension unavailable at a call site. Keep extension properties cheap and unsurprising; a property that performs an expensive or externally visible operation can mislead callers. This example computes from one value and stores nothing. Its checked Double also retains ordinary overflow behavior rather than promising unlimited arithmetic merely because the call reads fluently.

Practice one: isolate a contract

First change the batch threshold to four in both implementations and predict false for the existing two and three observations. Add a separate call for four and expect true. Hint: preserve an independent boundary expectation so the two programs cannot share an unnoticed off-by-one mistake. Keep the portable helper and external syntax clearly labeled while comparing results. Then change only one implementation deliberately and confirm that your written contract identifies the disagreement, rather than accepting whichever version happened to be compiled most recently.

Practice two: challenge an assumption

For another exercise, add a computed extension property that classifies an integer as nonnegative, with an equivalent portable helper. Test negative one, zero, and one. Hint: this is a stateless computation from the receiver, not an extra field attached to every integer. Move the extension container into a named namespace and explicitly import it from the program to examine scope. Do not simulate extension discovery by adding a runtime registry; the purpose of the external project is to let the real compiler resolve the actual declarations.

Review the resulting contract

Review extension blocks by separating receiver shape, member availability, domain logic, and the compiler environment. The external source demonstrates the syntax; the portable source makes its small logic easy to inspect on the current engine. Neither should be advertised as the other. A useful extension can improve readability without changing the receiver's fundamental ownership or representation. Before publishing an extension API, consider namespace collisions, future instance members, discoverability, and whether a property accurately communicates the cost and effects of the operation it exposes to consumers.

External .NET 10 SDK / stable C# 14. This project does not run in SharpRune's .NET 9 playground.

C# 14 extension blocks and properties lab

Set up the SDK project

  1. Save every listed file at its exact relative path in a fresh directory.
  2. Install a .NET 10 SDK supporting stable C# 14; check dotnet --version.
  3. Run dotnet run --project FeatureLab.csproj. Keep this external code separate from the .NET 9 playground.

Download complete SDK project ↗

Project files

Program.csDownload file ↗
Console.WriteLine($"two:{2.IsBatch}");
Console.WriteLine($"three:{3.IsBatch}");
Console.WriteLine($"double:{4.Double()}");
QuantityExtensions.csDownload file ↗
public static class QuantityExtensions
{
    extension(int quantity)
    {
        public bool IsBatch => quantity >= 3;
        public int Double() => checked(quantity * 2);
    }
}
FeatureLab.csprojDownload file ↗
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <LangVersion>14.0</LangVersion>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>

Expected result

Expected console output, in this order: two:False three:True double:8 The portable companion compares the same small rule using older syntax; it does not compile or emulate the new feature.

Use the listed SDK and dependencies for this project. Open C# practice runs the separate portable exercise.

C# companion practice

using System;
Console.WriteLine($"two:{BatchRules.IsBatch(2)}");
Console.WriteLine($"three:{BatchRules.IsBatch(3)}");
Console.WriteLine($"double:{BatchRules.Double(4)}");
static class BatchRules
{
    public static bool IsBatch(int quantity) => quantity >= 3;
    public static int Double(int quantity) => checked(quantity * 2);
}
Open C# practice

C# practice output

two:False
three:True
double:8
Try this
Run this helper, then compile the actual external extension block. Call syntax does not change the threshold requirement.

Further reading