Select Page

An extension method can compile in one file and appear to disappear in another. The useful question is: which namespace makes this operation available, and which method does the compiler actually select?

This tutorial builds a ticket-label helper, then deliberately breaks its lookup. You will diagnose a missing import, resolve two competing extensions, and see why an instance method wins.

Tested environment: .NET SDK 10.0.402, target net10.0, C# 14.0, Linux container; no extra packages. Each complete program below replaces Program.cs in a separate .NET 10 console project. Do not combine the programs. The examples use the classic this-parameter syntax. C# 14 also supports extension blocks; that newer syntax is outside this tutorial. Microsoft documents .NET 10’s C# 14 default; the custom-extension guide explains both declaration forms.

Make a small operation available through its namespace

Suppose a ticket screen needs labels such as [CS-24]. Put that operation in Station.TicketText, so a caller imports the feature it wants. Here the extension accepts a non-null string; it is a display helper, not ticket-ID validation.

using System;
using Station.TicketText;

Console.WriteLine("  CS-24  ".ToTicketLabel());
Console.WriteLine(TicketLabels.ToTicketLabel("  CS-24  "));

namespace Station.TicketText
{
    public static class TicketLabels
    {
        public static string ToTicketLabel(this string value) =>
            $"[{value.Trim()}]";
    }
}

Exact output:

[CS-24]
[CS-24]

The first call uses extension syntax; the second calls the static method directly. Both run the same helper. The this string value parameter receives the string on the left of the dot. The containing class is top-level, nongeneric, and static; its public method is static too. These are the declaration and calling rules for classic extension methods. Nothing is added to the definition of string.

If the helper lives in another project, reference that project or assembly as well as importing its namespace. An import alone does not supply the assembly. Microsoft’s guide lists both requirements.

Reproduce a missing extension: CS1061

In the first program, remove using Station.TicketText; and remove the second Console.WriteLine statement, leaving the extension-style call and the namespace declaration intact. That exact variant fails with CS1061: the compiler cannot find ToTicketLabel on string or an accessible extension method for it.

Restore the import to make the call compile again. This isolates namespace scope as the problem: the helper’s source was present all along. In a real project, also check the reference, receiver type, and accessibility. CS1061 describes a missing member or accessible extension method.

For discoverability, document the namespace beside the method name in your library’s usage example. A recognizable feature namespace is a practical way to tell consumers where to look. Our namespace lesson explains the underlying organization, and the methods lesson covers parameters and calls.

Importing two helpers can create an ambiguous call

These two namespaces deliberately expose the same extension name and receiver type:

using System;
using Station.CompactLabels;
using Station.VerboseLabels;

Console.WriteLine("CS-24".ToTicketLabel());

namespace Station.CompactLabels
{
    public static class TicketLabels
    {
        public static string ToTicketLabel(this string value) => $"[{value}]";
    }
}

namespace Station.VerboseLabels
{
    public static class TicketLabels
    {
        public static string ToTicketLabel(this string value) => $"Ticket: {value}";
    }
}

This program fails with CS0121. Both extensions are candidates for this call, and neither is better. Swapping these two imports also produced CS0121 in a second compiler test. The diagnostic is an ambiguous overload-resolution error.

Keep the declarations and replace the call with this line to select the compact helper explicitly:

Console.WriteLine(Station.CompactLabels.TicketLabels.ToTicketLabel("CS-24"));

Exact output:

[CS-24]

The qualified static call names the containing type, bypassing the ambiguous extension-style call. Alternatively, remove the unwanted import when the file only needs one format. For a library you control, prefer method names that communicate distinct jobs; consumers should not have to resolve formatting policy by guessing which import to keep.

An extension does not override an instance method

A matching instance member has priority over an extension. This is compile-time binding, as Microsoft’s extension guide explains:

using System;
using Station.TicketText;

Ticket ticket = new();
Console.WriteLine(ticket.Label());
Console.WriteLine(TicketLabels.Label(ticket));

public sealed class Ticket
{
    public string Label() => "instance";
}

namespace Station.TicketText
{
    public static class TicketLabels
    {
        public static string Label(this Ticket ticket) => "extension";
    }
}

Exact output:

instance
extension

The dot call selects Ticket.Label(). Naming TicketLabels explicitly selects the extension’s static method. This is why an extension should not be a plan for overriding behavior in an existing class.

Check these three things when a helper disappears

  1. Availability: Is the defining assembly referenced, is the extension accessible, and is its namespace in scope?
  2. Competition: Does another imported namespace expose an equally applicable extension? Use an explicit static call to select one in the case shown above.
  3. Existing behavior: Is an applicable instance method taking priority? Inspect the receiver’s declared type and its members before changing the extension.

Use an extension when the operation reads naturally beside the receiver and has a clear feature boundary. Keep a normal static helper when naming its owner makes the call easier to understand. The import-and-collision examples give you a concrete way to judge that tradeoff for your own API.

Share This