Extension Methods & Extension Members
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.
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}";
}Rohtak True Rs 1,299.50 Pune
"ROHTAK".ToTitleCase()looks like an instance method, but the compiler turns it intoTextExtensions.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
usingdirective, exactly as youusing System.Linqto get LINQ. N2uses 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)
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..];
}
}True False 4 ****01 ************4444
extension(string text)says "these members extendstring, and inside them the string is calledtext". The nametextis the receiver parameter, available in every member of the block.IsMobileNumberandWordCountare extension properties: you read them without brackets, liketext.Length. Before C# 14 they would have had to be methods,IsMobileNumber().Maskedis 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.
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;
}
}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:
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);
}
}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
stringever gets its ownWordCountproperty, 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" (errorCS1061) 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 asType.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
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;
}
}
}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?
The this modifier on the first parameter lets the method be called like an instance method.
2. What can C# 14 extension blocks add that classic extension methods could not?
Extension blocks support properties, static members and operators; they still cannot store state.
3. If a type already has an instance method with the same signature as your extension method, which one is called?
Real members always take priority; the extension is silently ignored.
4. How do you call a static extension member FromPaise declared in extension(decimal) { ... }?
Static extension members are called on the extended type itself.
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
