Extension Methods
Extension methods look like you’re adding new methods to existing types — including types you don’t own — but they’re pure syntactic sugar. Under the hood, they’re just static methods that the compiler lets you call with instance-method syntax.
What is an extension method (really)?
An extension method is a static method in a static class whose first parameter is preceded by this.
public static class StringExtensions
{
public static bool IsNullOrWhiteSpace(this string? value)
=> string.IsNullOrWhiteSpace(value);
}
Usage (instance-like syntax)
string? s = " hi ";
bool b = s.IsNullOrWhiteSpace(); // looks like an instance method
What does the compiler turn it into?
The call
s.IsNullOrWhiteSpace();
Is compiled exactly as:
StringExtensions.IsNullOrWhiteSpace(s);
No hidden magic, no runtime patching — just a normal static call with the receiver passed as the first argument. That’s why extension methods:
- cannot access private members,
- cannot be virtual/override,
- and do not participate in runtime polymorphism.
How does that differ from an instance method?
Dispatch (binding)
- Instance methods use virtual dispatch (if virtual) and are selected at runtime based on the actual object type.
- Extension methods are chosen at compile time based on the static type in scope. There’s no vtable lookup.
public class Base { public virtual string Who() => "Base"; }
public class Derived : Base { public override string Who() => "Derived"; }
public static class BaseExtensions
{
public static string Who(this Base _) => "Extension";
}
Base x = new Derived();
Console.WriteLine(x.Who());
// Calls the instance method -> "Derived" (virtual dispatch wins)
Overload precedence If there’s both an instance method and an extension method with the same name/signature, the instance method wins.
public class Foo
{
public void Bar(int _) => Console.WriteLine("Instance");
}
public static class FooExtensions
{
public static void Bar(this Foo _, int __) => Console.WriteLine("Extension");
}
var f = new Foo();
f.Bar(1); // "Instance"
Accessibility Extension methods can only call public (or otherwise accessible) members on the target type, because they’re just external static methods.
Namespaces and discovery
For an extension method to be available, the static class’s namespace must be in scope via using (or global using). The compiler scans all in-scope extension classes to consider candidate methods during overload resolution.
If multiple extension classes define the same method name, normal overload resolution rules apply; there’s no runtime ambiguity — it’s resolved at compile time.
Interaction with LINQ (and providers like EF)
LINQ is mostly a set of extension methods:
IEnumerable<T>operators (e.g., Where, Select) live inSystem.Linq.Enumerableand execute in-memory.IQueryable<T>operators live inSystem.Linq.Queryableand build expression trees for providers (like EF) to translate.
Which one you call depends on the static type of the sequence in scope:
IEnumerable<User> users1 = list; // Enumerable.Where -> in-memory
IQueryable<User> users2 = db.Users; // Queryable.Where -> expression tree -> SQL
When should I use extension methods?
- To add fluent helper APIs to types you don’t own.
- To keep utility logic close to the type it operates on (discoverable via IntelliSense).
- To provide alternate “shapes” for existing types (e.g., AsSpan, AsTask, AsEnumerable patterns).
Avoid overusing them for core domain behavior that should live on your own types as instance methods.
Quick reference: differences at a glance
| Aspect | Instance method | Extension method |
|---|---|---|
| Declaration | In the type | Static method in static class (this T) |
| Dispatch | Virtual (optional), runtime | Static, compile-time |
| Can override? | Yes (if virtual/abstract) | No |
| Access to private members | Yes | No |
| Requires using | No | Yes (for the extension’s namespace) |
| Works on null receiver | N/A (calling on null throws) | Yes (it’s just a parameter) |
Final Thoughts
- Extension methods are static methods dressed up with instance syntax.
- Instance methods have priority and can be virtual (runtime dispatch); extension methods are compile-time only.
- Choose extension methods for helpers/utilities and keep core behavior inside your types.


