C# 14 Extension Members on Static Classes: Static Members Only

A C# 14 extension block can target a static class such as Directory, but only static members can be added. Instance members are rejected with CS0721 or CS9303. This article maps the boundary to the receiver form and covers the alternatives for instance-style calls.

Overview

This article clarifies what does and does not work when a C# 14 (.NET 10) extension block targets a static class such as System.IO.Directory.

The short answer: a static class can be the target of an extension block, but only static members can be added. Declaring an instance member is a compile error.
Which of the two you get is decided by the receiver form — whether you write the type alone or give the receiver a parameter name.

The article also covers the alternatives when instance-style calls are required, and the evolution of extension member support from C# 3.0 to C# 14.

A table showing which extension block declarations are accepted for the static class Directory. With a type-only receiver, a static member compiles and an instance member fails with CS9303. With a named receiver parameter, the block itself fails with CS0721.
What an extension block can declare when the target is a static class, verified by building against .NET 10 SDK 10.0.302 with LangVersion 14.0. Once the receiver has a name, the block fails with CS0721 regardless of the members that follow.

Prerequisites / Environment


Problem

Whether an extension block targeting a static class compiles depends on how it is written.
Here is the form that does not compile.

using System.IO;

public static class DirectoryExtensions
{
    // error CS0721: 'Directory': static types cannot be used as parameters
    extension(Directory directory)
    {
        public void DeleteIfExists(string path)
        {
            try
            {
                Directory.Delete(path, true);
            }
            catch (DirectoryNotFoundException)
            {
                // Nothing to do when it is already gone.
            }
        }
    }
}

The error is reported at Directory inside extension(Directory directory).
In other words, the receiver declaration itself is rejected, regardless of what the members contain.

Remove the parameter name directory from the receiver and make the member static, and the same Directory target compiles.

using System.IO;

public static class DirectoryExtensions
{
    // This one compiles
    extension(Directory)
    {
        public static void DeleteIfExists(string path)
        {
            try
            {
                Directory.Delete(path, true);
            }
            catch (DirectoryNotFoundException)
            {
                // Nothing to do when it is already gone.
            }
        }
    }
}

At the call site it resolves as though it were a static method that Directory always had.

Directory.DeleteIfExists(@"C:\Temp\TargetDir");

The restriction is sometimes summarized as “extension members cannot be added to static classes,” but what actually cannot be added is instance members.


Cause

The receiver can be written in two forms

The receiver of an extension block can be written either as a type alone or as a parameter.
This is not a stylistic choice: it changes which kinds of members can be declared.

Receiver form Members that can be declared Static class allowed as target
extension(Directory) Static members only Yes
extension(Directory directory) Instance members (and static members) No

The language specification states that if the receiver parameter is named, the receiver type may not be static.
Once the receiver has a name it is an ordinary parameter, so the existing rule that a static class cannot be used as a parameter type applies unchanged.

Why a static class cannot be a parameter type

C# forbids using the name of a static class as the type of anything that holds a value.
All of the following are compile errors.

Usage Error
Directory myDir; CS0723: cannot declare a variable of static type
List<Directory> list; CS0718: static types cannot be used as type arguments
void M(Directory d) CS0721: static types cannot be used as parameters

extension(Directory directory) is the same situation as the last row, and the reported error is likewise CS0721.

Note that the name of a static class is not entirely unusable as a type.
typeof(Directory) is legal, and member access such as Directory.Exists(path) obviously works.
What is forbidden is using it as the type of a value-holding location: a variable, a parameter, or a type argument.
extension(Directory) compiles precisely because it declares no location that receives the receiver value.

An unnamed receiver cannot carry instance members

Conversely, declaring an instance member inside the extension(Directory) form produces a different error.

extension(Directory)
{
    // error CS9303: cannot declare instance members in an extension block
    //               with an unnamed receiver parameter
    public void DeleteIfExists(string path) { }
}

The body of an instance member refers to the receiver value, so a name for that value is required.
Without a name, no instance member can be declared.

Caught between these two errors, there is no way to write an instance-style extension member on a static class.


Solution

The right option depends on what you are trying to achieve.


Implementation

Option A: Static extension member

On C# 14 or later this is the most direct approach. It adds a member that looks as though Directory always had it.

using System.IO;

namespace MyLib;

public static class DirectoryExtensions
{
    extension(Directory)
    {
        /// <summary>
        /// Deletes the directory at the specified path if it exists.
        /// </summary>
        public static void DeleteIfExists(string path)
        {
            try
            {
                Directory.Delete(path, true);
            }
            catch (DirectoryNotFoundException)
            {
                // Nothing to do when it is already gone.
            }
        }

        /// <summary>The default working directory.</summary>
        public static string DefaultRoot => @"C:\Temp";
    }
}

The call site looks as follows.

using System;
using System.IO;
using MyLib; // omit this and you get CS0117

Directory.DeleteIfExists(@"C:\Temp\TargetDir");
Console.WriteLine(Directory.DefaultRoot);

As the comment notes, without a using for the namespace that declares the extension members, the compiler reports CS0117Directory does not contain a definition for DeleteIfExists.
This is the same constraint that applies to classic extension methods, not something specific to static extension members.

Option B: Extension member on DirectoryInfo

DirectoryInfo is an ordinary instantiable type, so its receiver can carry a parameter name.
This is the option to use when calls should be consistently instance-style.

using System.IO;

public static class DirectoryInfoExtensions
{
    // DirectoryInfo is not a static class, so the parameter form is allowed
    extension(DirectoryInfo directoryInfo)
    {
        /// <summary>
        /// Deletes the directory if it exists.
        /// </summary>
        public void DeleteIfExists()
        {
            try
            {
                directoryInfo.Delete(true);
            }
            catch (DirectoryNotFoundException)
            {
                // Nothing to do when it is already gone.
            }
        }
    }
}

The call site looks as follows.

var dir = new DirectoryInfo(@"C:\Temp\TargetDir");
dir.DeleteIfExists();

Creating a DirectoryInfo instance is an extra step, but it is convenient when the path is resolved once and several operations follow.

Option C: Static helper class

When C# 13 or earlier must be supported, extension blocks are unavailable, so a conventional static class is the fallback.

using System.IO;

public static class DirectoryHelper
{
    /// <summary>
    /// Deletes the directory at the specified path if it exists.
    /// </summary>
    public static void DeleteIfExists(string path)
    {
        try
        {
            Directory.Delete(path, true);
        }
        catch (DirectoryNotFoundException)
        {
            // Nothing to do when it is already gone.
        }
    }
}
DirectoryHelper.DeleteIfExists(@"C:\Temp\TargetDir");

The Directory.DeleteIfExists(...) form is not available, but the location of the method is unambiguous.


Notes


Alternatives / Comparison

Approach Call form Pros Cons Best suited for
Static extension member (Option A) Directory.DeleteIfExists(path) Looks identical to the standard API C# 14 or later only; a missing using is easy to misdiagnose Filling gaps in a static class API on C# 14 or later
Extension member on DirectoryInfo (Option B) dir.DeleteIfExists() Consistent instance-style calls Requires an instance; C# 14 or later only Resolving a path once and performing several operations
Static helper class (Option C) DirectoryHelper.DeleteIfExists(path) No version dependency; obvious location Call form differs from the standard API Supporting C# 13 or earlier

Supplementary: Evolution of Extension Members from C# 3.0 to C# 14

Support for attaching members to existing types has expanded incrementally across C# versions.

Version Platform Key change
C# 3.0 .NET Framework 3.5 Extension methods introduced. A this-prefixed first parameter inside a public static class allows instance methods to be attached to existing types. This feature underpins LINQ.
C# 7.2 .NET Core 2.0 / .NET Framework 4.7.2 Improved support for value types. The ref this and in this modifiers became available, enabling extension methods on large structs to pass the receiver by reference without copying.
C# 14 .NET 10 Extension member syntax (extension block) introduced. In addition to the this-parameter form, members can be declared inside an extension(Type) block, which supports properties, indexers, and operators as well as methods — and static members.

The this-parameter syntax can only declare instance extension methods; before C# 14 there was no way to attach a static member at all.
Adding a member such as Directory.DeleteIfExists(...) to a static class first became possible with the C# 14 extension block.


Summary

Targeting a static class with an extension block is possible.
What is not possible is adding an instance member: naming the receiver turns the static class into a parameter type, which produces CS0721.