Why This Matters When AI Can Just Write the Code
An AI assistant can generate an extension method for you in seconds — a one-liner with this in front of the first parameter, done. So why does the underlying concept deserve real understanding rather than just trusting the generated snippet? Because extension methods are one of the easiest C# features to use incorrectly while still having it compile and appear to work, and the failure modes are exactly the kind that only show up once real usage patterns emerge — not in the small demo an AI tool shows you.
Here’s a concrete version of the problem: extension methods can never actually override or replace behaviour on the type they extend — they can only add the appearance of new members. If an AI tool generates an extension method with the same name as a real instance method that gets added to the target type later (by you, by a library update, or because you misjudged which member already existed), the real instance method silently wins every single time, with zero warning, zero error, and no visible sign in your code that anything changed. You need to already understand the resolution rules covered in this post — that instance members always beat extension methods, silently — to even suspect this is happening when a call stops behaving the way you expect after an unrelated update.
There’s a second, more subtle reason this topic rewards real understanding: knowing when an extension method or extension member is the right tool — versus when it’s papering over a design problem you should actually be solving with an interface, a wrapper type, or a genuine change to a type you do own — is a judgement call, not a syntax question. An AI tool asked to “add a helper to this type” will readily generate an extension method whether or not that’s actually the right long-term choice, because generating a working answer is different from generating the right answer for your codebase’s shape. Being able to read a codebase full of extension methods (your own, a teammate’s, or a library’s) and understand exactly what’s really happening — a static method dressed up to look like an instance member, nothing more — is a reading-comprehension skill that pays off every time you touch a real project, regardless of who wrote the extension in the first place.
Why This Post Exists
Sometimes you need a type to have a method, property, or operator that it doesn’t currently have — and you either can’t modify the type’s source code (it belongs to the .NET framework, or a third-party library), don’t want to modify it (adding an inheritance relationship or a wrapper just for one helper feels heavy-handed), or structurally can’t modify it in the way you’d like (you can’t retroactively add an interface implementation to a sealed class you don’t own). C# solves this with extension methods, a feature that’s existed since C# 3.0, and — as of C# 14 — a considerably more powerful evolution of the same idea called extension members, which finally allow properties, static members, and operators to be added the same way methods always could.
By the end of this post you should be able to explain precisely what an extension method actually compiles down to and why that explains every rule governing how they behave; write both the classic (this-parameter) syntax and the modern C# 14 extension block syntax; correctly predict member resolution when an extension and a real instance member share a name, and when two unrelated extensions collide with each other; understand how extension methods interact with generics, interfaces, value types, and null; recognise LINQ as the single most consequential real-world application of this feature; and know exactly which new capabilities C# 14’s extension blocks unlock and which fundamental limitation — no genuine new fields — still applies to both syntaxes, and always will.
The Core Idea: Extension Methods Are a Compiler Illusion
Here is the single fact that explains almost everything else in this post, so it’s worth establishing first, very precisely: an extension method is an ordinary static method, and the compiler is simply allowed to call it using instance-method syntax as a special case. Nothing about the target type changes at all — not its fields, not its actual member list, not anything visible via reflection on the type itself. The “extension” only exists from the caller’s point of view, as a trick the compiler performs while translating your source code into the method calls that actually get compiled.
Here’s the classic syntax, which has worked since C# 3.0:
public static class StringExtensions
{
public static int WordCount(this string str) =>
str.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}
Two things make this an extension method rather than an ordinary static helper: it lives in a static class (a hard requirement — extension methods cannot be declared anywhere else, and the class itself cannot be generic or nested), and its first parameter has the this modifier, which tells the compiler “this parameter is the thing being extended; let people call this method as if it belonged to that type.”
string sentence = "The quick brown fox";
int count = sentence.WordCount(); // looks like an instance method call
This line looks exactly like calling a genuine instance method — but the compiler is silently rewriting it, behind the scenes, into an ordinary static method call:
int count = StringExtensions.WordCount(sentence); // what actually gets compiled
Both forms are available to you as the caller — you can write either one, and they mean exactly the same thing. sentence.WordCount() and StringExtensions.WordCount(sentence) compile to identical IL (the intermediate code the .NET runtime actually executes) — the dot-syntax version is purely a convenience the compiler offers you, with zero runtime difference between the two forms. This is worth sitting with, because nearly every rule and limitation covered in the rest of this post follows directly from this one fact: you are not actually adding a member to the type. You are writing a static method, and asking the compiler to let you call it using nicer syntax.
A direct consequence: string doesn’t actually gain a WordCount member
Because nothing about string itself changed, reflection-based code, other languages targeting the CLR without the same extension-method sugar, and anything that inspects string’s actual member list will never see WordCount there at all — it doesn’t exist on string. It exists only as a static method that C# is willing to let you call with instance syntax, and only when the extension method’s containing namespace is in scope via a using directive:
using MyProject.StringExtensions; // required — without this, sentence.WordCount() won't compile at all
If you don’t using the namespace containing the extension method, sentence.WordCount() simply fails to compile — the method genuinely isn’t visible, because as far as the language is concerned outside that using scope, it doesn’t exist as a callable member of string at all.
A second direct consequence: no access to private members
Because an extension method is genuinely just a static method living outside the target type entirely, it only ever has access to the target type’s public (or otherwise externally visible) members — exactly the same access any other unrelated piece of code would have. It cannot reach into private or protected fields the way a real instance method defined inside the class could.
class BankAccount
{
private decimal _balance;
public decimal Balance => _balance;
}
public static class BankAccountExtensions
{
public static void AddInterest(this BankAccount account, decimal rate)
{
// account._balance += ...; // ILLEGAL — _balance is private, and this is just an outside static method
}
}
This is a genuine, and often clarifying, limitation: it means extension methods can only ever build on top of a type’s existing public surface — they can combine, reformat, or compute from what’s already exposed, but they can never reach in and manipulate private internal state the way a genuine member of the class could. If you find yourself wanting an “extension” that needs private access, that’s a strong signal you actually need a real member on the type itself, not an extension.
Extension methods and null: a real behavioural difference from instance methods
Because an extension method call is secretly just a static method call, it follows the rules of an ordinary static call — including a rule that’s genuinely different from how real instance methods behave: you can call an extension method on a null reference without an exception being thrown, as long as the extension method itself is written to handle that gracefully.
public static class StringExtensions
{
public static bool IsNullOrEmpty(this string? str) => string.IsNullOrEmpty(str);
}
string? name = null;
bool result = name.IsNullOrEmpty(); // does NOT throw! prints/evaluates to true
This looks alarming at first — calling a method on null normally throws a NullReferenceException immediately, as covered in any discussion of null handling. But remember: name.IsNullOrEmpty() isn’t really calling a method on name at all in the way a genuine instance method call would; it’s calling StringExtensions.IsNullOrEmpty(name), passing name as an ordinary argument. Passing null as an argument to a static method is completely unremarkable and doesn’t throw anything by itself — whether an exception happens at all depends entirely on what the method’s body actually does with that argument. This is precisely why string.IsNullOrEmpty(str) (a real static method in the .NET standard library, not even an extension) has always been safely callable with null — and it’s a useful, idiomatic pattern: writing null-safe extension methods that let callers skip an explicit null check beforehand, as long as you clearly document and rely on the extension itself handling null correctly rather than assuming its caller already ruled that out.
Member Resolution: Real Members Always Win, Silently
Because an extension method is only ever a fallback the compiler reaches for when it can’t find a genuine matching instance member, there’s a strict, important priority order: if a type already has an instance member with a matching name and signature, that real member is always called instead of any extension method with the same name — with no warning, no ambiguity error, nothing. The extension method simply never gets a chance to run.
class Greeter
{
public string Greet() => "Hello from the real method!";
}
public static class GreeterExtensions
{
public static string Greet(this Greeter g) => "Hello from the extension!";
}
var g = new Greeter();
Console.WriteLine(g.Greet()); // "Hello from the real method!" — the extension is completely ignored
This is the exact scenario flagged in the introduction to this post: if Greeter didn’t originally have a Greet() method, and you wrote the extension expecting it to be called, then a later change that adds a real Greet() method to Greeter — even one with a different implementation, added by someone who’s never heard of your extension — silently and permanently shadows your extension method for every caller, forever, with no compiler error anywhere to flag that anything changed. This is a genuinely realistic bug in evolving codebases and library upgrades, and it’s a direct, unavoidable consequence of what an extension method fundamentally is: a fallback, never a true member, always losing to the real thing.
What happens when two different extensions collide
The “silent, no-error” behaviour above is specific to a real instance member competing with an extension — a real member always wins outright, and the compiler doesn’t even consider it a conflict worth mentioning. The situation is different when two separate extension methods, from two different static classes, both apply to the same type with the same name and signature, and both are in scope via using directives at the same time. Here, the compiler generally does flag the situation, because there’s no real member to automatically defer to:
namespace LibraryA { public static class Ext { public static string Describe(this int i) => "From A"; } }
namespace LibraryB { public static class Ext { public static string Describe(this int i) => "From B"; } }
using LibraryA;
using LibraryB;
int x = 5;
// x.Describe(); // Compile ERROR — ambiguous call between LibraryA.Ext.Describe and LibraryB.Ext.Describe
When this happens, you resolve the ambiguity by falling back to the fully-qualified static method call syntax — calling LibraryA.Ext.Describe(x) directly bypasses the ambiguity entirely, since you’re no longer asking the compiler to search for a matching extension; you’re naming the exact static method you want. This is precisely why extension members still need to live inside a named static class even under the modern C# 14 syntax, as we’ll in more detail later — that container name is your escape hatch out of exactly this kind of collision.
There’s one more wrinkle worth knowing: if the two competing extensions are visible at different levels of scope — for example, one brought in by a using at the top of your file, and another available because it’s declared in the same namespace your code is already in, with no using needed — C# does have a preference order (closer, more specific scopes win over using-imported ones). But when both candidates are equally “close” (as in the two-using-directives example above), the result is a genuine compile-time ambiguity error, not a silent pick of one over the other — which is a meaningfully different, safer outcome than the silent shadowing that happens when a real instance member is involved.
Generic Extension Methods
Extension methods can be generic, and this is, in practice, one of their most powerful and common uses — it’s exactly how a huge portion of the .NET standard library’s most useful helpers (LINQ chief among them) are able to work across virtually any collection type at once, rather than needing a separate hand-written version for every possible element type.
public static class EnumerableExtensions
{
public static T? SecondOrDefault<T>(this IEnumerable<T> source)
{
using var enumerator = source.GetEnumerator();
if (enumerator.MoveNext() && enumerator.MoveNext())
return enumerator.Current;
return default;
}
}
List<int> numbers = [10, 20, 30];
int second = numbers.SecondOrDefault(); // 20 — works for List<int>, and equally for any IEnumerable<T>
string[] words = ["a", "b", "c"];
string? secondWord = words.SecondOrDefault(); // "b" — the exact same method, now working on strings instead
Notice that the caller never has to specify
public static class ComparableExtensions
{
public static T Clamp<T>(this T value, T min, T max) where T : IComparable<T>
{
if (value.CompareTo(min) < 0) return min;
if (value.CompareTo(max) > 0) return max;
return value;
}
}
int clamped = 150.Clamp(0, 100); // 100 — works because int implements IComparable<int>
This pattern — a generic extension constrained to an interface — is an extremely common and idiomatic way to add a single, reusable piece of behaviour across every type that satisfies some capability, without needing to touch any of those types individually.
Extension Methods on Interfaces
You can write an extension method whose receiver type is an interface rather than a concrete class or struct — and this turns out to be one of the single most important applications of the entire feature, because it lets you add functionality that automatically becomes available to every type that implements that interface, all at once, without touching any of them.
interface IShape { double Area(); }
public static class ShapeExtensions
{
public static bool IsLargerThan(this IShape shape, IShape other) => shape.Area() > other.Area();
}
class Circle : IShape { public double Radius; public double Area() => Math.PI * Radius * Radius; }
class Square : IShape { public double Side; public double Area() => Side * Side; }
var c = new Circle { Radius = 2 };
var s = new Square { Side = 3 };
Console.WriteLine(c.IsLargerThan(s)); // works on Circle, Square, or any future IShape implementer, automatically
IsLargerThan was written once, against the interface, and immediately works for Circle, Square, and any type anyone writes in the future that implements IShape — including types that don’t exist yet at the moment this extension was written. This is a genuinely powerful multiplier: extending an interface effectively extends every current and future implementer of that interface simultaneously, which is a much larger reach than extending one specific concrete class.
A subtlety: which members are visible depends on the static type of the expression
Because member resolution (including extension method resolution) in C# happens at compile time based on an expression’s declared type — not its actual runtime type — an extension written for a concrete class won’t be found if you’re holding a reference to that object through a less specific interface or base type, unless the extension itself was also written against that broader type (or the object’s actual compile-time type at the call site).
public static class CircleExtensions
{
public static double Diameter(this Circle c) => c.Radius * 2;
}
IShape shape = new Circle { Radius = 2 };
// shape.Diameter(); // Compile ERROR — Diameter() only extends Circle, but 'shape' is statically typed as IShape
Even though the object really is a Circle at runtime, shape.Diameter() fails to compile, because the compiler only ever considers the extensions available for the expression’s declared type (IShape here), never the object’s actual runtime type. This is a direct, if easy-to-forget, consequence of extension resolution being a purely compile-time, static-typing-based mechanism — there’s no runtime lookup happening at all, unlike genuine virtual method dispatch.
Extension Methods and Value Types: Copies, ref, and in
Extension methods work perfectly well on struct/record struct receivers too, but it’s worth understanding precisely what gets passed, because it directly affects both correctness and performance.
By default, just like an ordinary method parameter, a value-type receiver is passed by value — meaning the extension method receives an independent copy of the struct, and any mutation performed inside the extension method has no effect on the original value the caller passed in:
struct Counter { public int Value; }
public static class CounterExtensions
{
public static void Increment(this Counter c) => c.Value++; // mutates a COPY, not the caller's original
}
var counter = new Counter { Value = 0 };
counter.Increment();
Console.WriteLine(counter.Value); // still 0 — the extension mutated its own local copy, not 'counter'
This is exactly the same “value types copy on pass” behaviour that applies to any ordinary method call — the extension-method call syntax doesn’t change that underlying rule at all, which is easy to forget given how much the dot-syntax makes it look like you’re operating on the original instance.
If you genuinely need the extension to mutate the caller’s original struct, you can mark the receiver parameter ref, exactly as you would for any ordinary method:
public static class CounterExtensions
{
public static void Increment(this ref Counter c) => c.Value++; // now mutates the CALLER's actual struct
}
var counter = new Counter { Value = 0 };
counter.Increment();
Console.WriteLine(counter.Value); // 1 — the extension mutated the real, original struct this time
Conversely, if the struct being extended is large and you want to avoid the performance cost of copying it on every call, but you don’t need to mutate it, marking the receiver in (or, in the modern extension block syntax, using an in receiver) passes the struct by reference for efficiency while still preventing the extension from modifying it — giving you the performance benefit of ref without giving up the safety of read-only access.
What C# 14 Changes: From “Methods Only” to “Extension Everything”
For its entire history prior to C# 14, the extension method feature had a real, sharp limitation: it only worked for methods. You could make something look like sentence.WordCount(), but you could never make something look like a genuine property (sentence.WordCount, no parentheses), a static member on the type itself (string.SomeHelper), or an operator (p1 + p2 for some type p1/p2 you don’t own). Workarounds existed — writing GetWordCount() instead of a true property, for instance — but they always looked and felt like methods pretending to be something else, because that’s exactly what they were.
C# 14 (shipped with .NET 10, November 2025) introduces a genuinely new syntax — the extension block — that removes this limitation entirely, while keeping every classic this-parameter extension method you’ve already written fully working, unchanged, forever. The two syntaxes coexist deliberately; you never need to migrate existing code, and you can freely mix both styles within the same static class.
Here’s the same WordCount example, rewritten using the new extension block syntax:
public static class StringExtensions
{
extension(string str)
{
public int WordCount() =>
str.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}
}
The extension(string str) block declares, once, that everything inside it extends string, with str available as the “receiver” — the instance being extended — throughout every member in the block, without needing to repeat a this string str parameter on each individual method the way the classic syntax requires. This alone is a real ergonomic improvement once you’re defining several related members for the same type, but the much bigger unlock is what kinds of members you’re now allowed to put inside that block.
Extension Properties
The clearest, most immediately useful addition in C# 14 is genuine extension properties — something with no parentheses, accessed exactly like a real property, computed on demand:
public static class StringExtensions
{
extension(string str)
{
public bool IsEmpty => string.IsNullOrEmpty(str);
public int WordCount => str.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}
}
string title = "Hello World";
Console.WriteLine(title.IsEmpty); // False
Console.WriteLine(title.WordCount); // 2 — accessed as a property, no parentheses at all
This matters more than it might first appear: before C# 14, the only way to expose something as a property-feeling piece of information via an extension was to write a method and simply accept that it would always need parentheses (str.WordCount()), which is a real, if small, readability cost for anything that’s conceptually a computed attribute of the value rather than an action being performed. IsEmpty, WordCount, and similar “describe a characteristic of this value” concepts read far more naturally as properties than as zero-argument methods, and now they finally can be.
Under the hood, this is still ultimately compiled down to a method (a property, extension or not, is always a getter/setter method pair at the IL level) — the underlying “it’s really just a static method with special call syntax” reality hasn’t changed at all. What’s changed is that the compiler now offers property-style call syntax as an option, not just method-style syntax, for extensions. Extension properties can also declare a setter, not just a getter — as long as the extension block has some way to actually apply the change, which in practice usually means the receiver type has a real, settable property or field of its own that the extension property’s setter delegates to underneath.
Static Extension Members
Before C# 14, every extension member — regardless of the syntax — always extended an instance of a type: you needed an actual string value in hand to call .WordCount() on. There was no way to add something that looked like a static member on the type itself, the way string.Empty or Guid.NewGuid() are static members you call on the type, not on an instance of it. C# 14 removes this restriction with static extension members, declared inside an extension block whose receiver has no parameter name (just the bare type):
public static class GuidExtensions
{
extension(Guid) // note: no parameter name — this block declares STATIC members on Guid itself
{
public static Guid Empty2 => Guid.Empty;
public static Guid CreateDeterministic(string input)
{
var hash = System.Security.Cryptography.SHA256.HashData(
System.Text.Encoding.UTF8.GetBytes(input));
return new Guid(hash.AsSpan(0, 16));
}
}
}
Guid deterministic = Guid.CreateDeterministic("some-stable-key"); // called on the TYPE, not an instance
Notice extension(Guid) here, rather than extension(Guid someGuid) — omitting the parameter name signals that this block declares members that appear directly on the type itself, not on instances of it. This is genuinely new ground for extensions: you can now make a sealed, third-party type appear to have its own static factory methods or constant-like properties, without ever touching its source, and without needing any instance of the type to access them.
A single static class can freely mix an instance-style extension block and a static-style extension block for the same target type, side by side, letting you organise both kinds of extension together:
public static class StringExtensions
{
extension(string str) // instance members
{
public bool IsEmpty => string.IsNullOrEmpty(str);
}
extension(string) // static members — note: no parameter name
{
public static string Repeat(string pattern, int count) =>
string.Concat(Enumerable.Repeat(pattern, count));
}
}
You can also declare a generic extension block, with type parameters and constraints living on the extension
Extension Operators
The next major new capability is the ability to define operators as extension members — something that was flatly impossible before C# 14, because operator overloads have always had to be declared as static members of one of the types directly involved in the operation, and you can’t add a static member to a type you don’t own using the classic this-parameter syntax at all.
public static class PointExtensions
{
extension(Point p)
{
public static Point operator +(Point a, Point b) => new Point(a.X + b.X, a.Y + b.Y);
}
}
var p1 = new Point(1, 2);
var p2 = new Point(3, 4);
var sum = p1 + p2; // Point(4, 6) — the + operator now works on a type you didn't write and can't modify
This is a meaningful capability unlock: previously, if you wanted + to work between two instances of a Point-like type from a library you don’t control, your only real option was to define your own wrapper type with its own + operator, and convert back and forth — a real amount of ceremony for what conceptually is a small addition. Extension operators let you add this kind of natural, domain-appropriate syntax directly to types you don’t own, the same way extension methods have always let you add natural-feeling method calls to types you don’t own. Just as with a normal operator overload, at least one of the operator’s parameters generally has to match the receiver type being extended, for the same reason ordinary operator overloading requires this: without that requirement, the compiler would have no principled way to decide which type “owns” the operator.
What’s Still Off-Limits: No Extension Fields, Ever
With properties, static members, and operators all newly available, it’s natural to wonder whether extension fields are coming too — real, honest-to-goodness storage slots added to an existing type. The answer is no, and it’s worth understanding why, because the reason is structural, not a temporary gap the language team just hasn’t gotten to yet.
Recall the foundational fact this entire post rests on: an extension member is, underneath everything, a static method (or a property, which is itself a getter/setter method pair). It doesn’t change the target type’s actual memory layout in any way — the type’s real, physical fields are exactly what they always were, unaffected by any extension you write. A genuine field requires the type itself to set aside actual storage for it at the moment each instance is created — and an extension block, being nothing but syntax sugar around static methods added after the type already exists and already has its layout fixed, has no mechanism to reach back in time and add storage to every existing and future instance of that type. This isn’t a missing feature — it’s a direct, unavoidable consequence of what “extending” a type without modifying its source can possibly mean.
public static class StringExtensions
{
extension(string str)
{
// public int CallCount; // ILLEGAL — extension blocks cannot declare fields, in any C# version
}
}
If you need genuine, persistent, per-instance storage attached to a type you don’t own, an extension member cannot provide it — you need a different tool entirely, such as a ConditionalWeakTable
Alongside fields, C# 14’s extension blocks also don’t support events, nested types, or constructors — extension blocks focus specifically on methods, properties (including indexer-style get/set), and operators.
Disambiguation: Why the Containing Static Class Still Matters
One detail worth knowing, especially once you’re organising a larger codebase: even though the new extension(…) block syntax no longer requires you to repeat the receiver type on every single member, you still need to wrap your extension blocks in an ordinary named static class, exactly as classic extension methods always required:
public static class StringExtensions // this name still matters!
{
extension(string str)
{
public bool IsEmpty => string.IsNullOrEmpty(str);
}
}
The reason this container class still matters, even though extension blocks feel almost like a language-level “reopen this type” mechanism, is disambiguation. If two different libraries each define an extension member with the same name for the same target type — a genuinely realistic scenario once extension members become widely used — the only way to resolve the resulting ambiguity in your calling code is to refer to one specific extension explicitly by its containing static class name (much like you’d disambiguate between two same-named classes in different namespaces). If extension members could exist with no named container at all, this escape hatch simply wouldn’t exist, and a naming collision between two unrelated libraries would become unresolvable rather than merely inconvenient.
LINQ: The Single Most Consequential Real-World Use of Extension Methods
It’s worth pausing on a concrete, large-scale example, because it’s easy to underestimate just how much of C#’s everyday feel actually rests on this one feature: LINQ (Language Integrated Query) — .Where(…), .Select(…), .OrderBy(…), .FirstOrDefault(…), and dozens of similar methods you use constantly — is, almost entirely, just a large, carefully designed set of generic extension methods on IEnumerable
Listnumbers = [1, 2, 3, 4, 5, 6];
var evens = numbers.Where(n => n % 2 == 0).Select(n => n * 10);
Where and Select are not real members of List
This is the single best real-world illustration of why interface-based extension methods are so powerful: the entire LINQ library was written once, against IEnumerable
When to Reach for Extension Members — and When Not To
Extension methods and extension members are the right tool specifically when you want to add behaviour to a type you don’t own (a .NET built-in type, a third-party library type) or when you want to add a natural-feeling helper to a type you do own, but where the helper doesn’t conceptually belong as a core responsibility of the type itself and would clutter its primary definition. Good, common uses include: LINQ-style query helpers, as just covered; formatting/validation/computed-shorthand helpers on built-in types (someString.IsValidEmail(), someDateTime.IsWeekend); and adding ergonomic syntax (properties, operators) to third-party domain types you use heavily but can’t modify.
They’re the wrong tool when what you actually need is genuine shared state (extension members can never hold fields), when the “extension” is really trying to override or replace behaviour a type already has (which we’ve previously showed silently doesn’t work the way you’d expect), or when you find yourself writing dozens of extensions that would be far better expressed as an interface implemented by a real wrapper type you control — extension members are a targeted convenience for adding syntax, not a general substitute for proper object-oriented design when you actually do have the ability to design the type relationships involved.
Summary
An extension method is, underneath its convenient dot-syntax, nothing more than an ordinary static method — the target type itself never actually changes, gains no new member in its real definition, has no access to the type’s private members, and can even be called safely on a null receiver, because it’s really just an argument being passed to a static method. This single fact explains every rule in this post: extension methods only work when their namespace is in scope, a genuine instance member with a matching name always silently wins over an extension with the same name (while two competing extensions instead produce a compile-time ambiguity error), and resolution is based purely on an expression’s compile-time type, never its runtime type. Extension methods can be generic and can extend interfaces rather than concrete types — a combination that, taken to its logical conclusion, is exactly how LINQ works: a library of generic extension methods on IEnumerable