EthicsComputerCourses
Join training +91 90532 10052

Extension Methods & Extension Members

C# · Lesson 25 of 43 Intermediate 7 min read Updated

Sometimes you wish a type had one more method. string has ToUpper but no ToTitleCase; decimal has no "format as rupees" method. You cannot edit the string class, because it belongs to .NET. Extension methods solve this: you write a static method in your own class, and C# lets you call it as if it were a normal method of string.

LINQ itself is built from extension methods: Where and Select are not part of List<T>, they extend IEnumerable<T>. In this lesson you will write classic extension methods, then use the new extension blocks of C# 14, which add extension properties and static extension members too.

Classic extension methods

An extension method is a static method in a non-generic, top-level static class. Its first parameter has the this modifier, and the type of that parameter is the type being extended.

C#
Console.WriteLine("ROHTAK".ToTitleCase());
Console.WriteLine("  ".IsBlank());
Console.WriteLine(1299.5m.ToRupees());
Console.WriteLine(TextExtensions.ToTitleCase("pUNE"));

static class TextExtensions
{
    public static string ToTitleCase(this string text) =>
        text.Length == 0 ? text : char.ToUpper(text[0]) + text[1..].ToLower();

    public static bool IsBlank(this string? text) => string.IsNullOrWhiteSpace(text);
}

static class MoneyExtensions
{
    public static string ToRupees(this decimal amount) => $"Rs {amount:N2}";
}
Output
Rohtak
True
Rs 1,299.50
Pune
  • "ROHTAK".ToTitleCase() looks like an instance method, but the compiler turns it into TextExtensions.ToTitleCase("ROHTAK"). The last line shows that normal static call; both forms are identical.
  • The extension class must be visible where you use it. If it lives in a namespace, add the matching using directive, exactly as you using System.Linq to get LINQ.
  • N2 uses group separators from the current culture; numbers below one lakh look the same in Indian and international formats.

Notice IsBlank(this string? text). Because an extension method is really a static method, it can be called on a null reference without crashing: ((string?)null).IsBlank() returns true. That is convenient, but it also means each extension method must decide how to handle null.

Extension blocks (C# 14)

C#
Console.WriteLine("9876543210".IsMobileNumber);
Console.WriteLine("12345".IsMobileNumber);
Console.WriteLine("Jaipur the Pink City".WordCount);
Console.WriteLine("124001".Masked());
Console.WriteLine("4111222233334444".Masked(4));

static class StringExtensions
{
    extension(string text)
    {
        public bool IsMobileNumber => text.Length == 10 && text.All(char.IsDigit);

        public int WordCount => text.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;

        public string Masked(int visible = 2) =>
            new string('*', text.Length - visible) + text[^visible..];
    }
}
Output
True
False
4
****01
************4444
  • extension(string text) says "these members extend string, and inside them the string is called text". The name text is the receiver parameter, available in every member of the block.
  • IsMobileNumber and WordCount are extension properties: you read them without brackets, like text.Length. Before C# 14 they would have had to be methods, IsMobileNumber().
  • Masked is an ordinary extension method written inside the block, with an optional parameter.

Extension properties cannot store data: there is nowhere to put a backing field, so they are always computed from the receiver. The two syntaxes compile to the same thing, so you can convert old this-style methods into a block without breaking any code that calls them.

Static extension members

An extension block without a receiver name declares static extension members. You call them on the type itself, as if they were static members of that type.

C#
decimal price = decimal.FromPaise(129950);
Console.WriteLine($"{price:F2}");
Console.WriteLine(decimal.GstRate);
Console.WriteLine($"{price * decimal.GstRate:F2}");

static class DecimalExtensions
{
    extension(decimal)
    {
        public static decimal FromPaise(long paise) => paise / 100m;

        public static decimal GstRate => 0.18m;
    }
}
Output
1299.50
0.18
233.91

decimal.FromPaise(...) and decimal.GstRate now read like built-in members of decimal. Payment gateways usually report amounts in paise, so a helper like this is genuinely useful. (The GST rate is hard-coded only to keep the example short.)

Generic extension blocks

An extension block can be generic, so one block serves every type. Here the block extends any IEnumerable<T>, which includes arrays, lists and LINQ queries:

C#
List<int> marks = [35, 72, 90, 64];
List<string> empty = [];
string[] cities = ["Pune", "Kochi", "Agra", "Surat"];

Console.WriteLine(marks.IsEmpty);
Console.WriteLine(empty.IsEmpty);
Console.WriteLine(string.Join(", ", cities.EveryOther()));
Console.WriteLine(string.Join(", ", marks.EveryOther()));

static class SequenceExtensions
{
    extension<T>(IEnumerable<T> source)
    {
        public bool IsEmpty => !source.Any();

        public IEnumerable<T> EveryOther() => source.Where((item, index) => index % 2 == 0);
    }
}
Output
False
True
Pune, Agra
35, 90

The type parameter goes on the extension keyword because the receiver uses it. Where has an overload whose lambda also receives each item's index, which EveryOther uses to keep positions 0, 2, 4 and so on.

Rules and good practice

  • Extension members must be declared in a top-level, non-generic, static class.
  • Real members always win. If string ever gets its own WordCount property, the compiler uses it and silently ignores your extension. Extensions cannot override anything.
  • Extensions cannot add state. If you need to store data, write a class that wraps the original object.
  • Do not extend types you own; just add the member to the class itself.
  • Keep extension classes small and focused, and place them in a namespace that callers import on purpose, so editors do not suggest dozens of unrelated helpers on every string.

Common mistakes

  • Forgetting the using. If the extension class is in another namespace, the method "does not exist" (error CS1061) until you import it.
  • Putting extensions in a non-static or nested class. The compiler rejects it. Use a top-level static class.
  • Assuming the receiver is never null. Extension methods can be called on null; check when it matters.
  • Name clashes with real members. The real member wins and your extension is ignored without any error.
  • Trying to give an extension property a backing field. Extension properties are computed only.

Key points

  • An extension method is a static method whose first parameter has this; you call it like an instance method.
  • LINQ is built from extension methods on IEnumerable<T>.
  • C# 14 extension(Type name) { ... } blocks declare extension methods and properties for that receiver.
  • extension(Type) { ... } without a name declares static extension members, called as Type.Member.
  • Extension blocks can be generic: extension<T>(IEnumerable<T> source).
  • Real members always take priority, and extensions cannot add stored data.

Exercise

Write a static class DateExtensions with a C# 14 extension block for DateTime that adds: a property IsWeekend, a property FinancialYear that returns a string like "2026-27" (Indian financial years run from 1 April to 31 March), and a method AddWorkingDays(int days) that skips Saturdays and Sundays. Test it with 14 November 2026 (a Saturday) and 15 March 2027.

Show solution
C#
DateTime sat = new(2026, 11, 14);
DateTime march = new(2027, 3, 15);

Console.WriteLine(sat.IsWeekend);
Console.WriteLine(sat.FinancialYear);
Console.WriteLine(march.FinancialYear);
Console.WriteLine(sat.AddWorkingDays(3).ToString("dd MMM yyyy"));

static class DateExtensions
{
    extension(DateTime date)
    {
        public bool IsWeekend =>
            date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday;

        public string FinancialYear
        {
            get
            {
                int start = date.Month >= 4 ? date.Year : date.Year - 1;
                return $"{start}-{(start + 1) % 100:D2}";
            }
        }

        public DateTime AddWorkingDays(int days)
        {
            DateTime result = date;
            while (days > 0)
            {
                result = result.AddDays(1);
                if (!result.IsWeekend)
                {
                    days--;
                }
            }
            return result;
        }
    }
}
Output
True
2026-27
2026-27
18 Nov 2026

From Saturday 14 November, the next working days are Monday 16, Tuesday 17 and Wednesday 18. Inside the block, result.IsWeekend uses the extension property itself, and 15 March 2027 still belongs to the 2026-27 financial year because it is before 1 April.

Quick Quiz

1. What makes a static method an extension method?

2. What can C# 14 extension blocks add that classic extension methods could not?

3. If a type already has an instance method with the same signature as your extension method, which one is called?

4. How do you call a static extension member FromPaise declared in extension(decimal) { ... }?

Learn C# with a mentor

Free lessons get you started. Our Programming & Software Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call