Inheritance matters, but the main thing is the namespace, from C# spec:
if normal call processing does not apply methods, an attempt is made to process the construct as an extension of the method call.
The exact process and example can be found in the C# specification, 7.6.5.2 Extension method invocations . The resolution process (where C is the set of possible type permissions and M is the set of extension methods):
Search C continues as follows:
β’ Starting from the closest namespace-encompassing declaration, continuing with each closing namespace declaration and ending with the compiling unit, successive attempts are made to find a set of candidates extension methods:
o If a given namespace or compilation unit directly contains declarations of a non-common Ci type with suitable extension methods Mj, then the set of these extension methods is a candidate set.
o If namespaces are imported using namespace directives in a given namespace or in a compilation block of a declaration not of general Ci type with suitable extension methods Mj, then the set of these extension methods is a set of candidates.
β’ If no candidate set is found in any namespace or compilation declaration, a compile-time error occurs.
β’ Otherwise, the congestion authorization applies to the recruitment of candidates as described in (Β§7.5.3). If no best method is found, a compile-time error occurs.
β’ C is the type in which the best method is declared as an extension of the method. Using C as a target, a method call is then processed as a call to a static method (Β§7.5.4).
Which, if I understand it correctly, means that it chooses the best extension method in the nearest namespace at compile time. It is very important to understand that there is no dynamic call, resolution is done statically (i.e., in compiletime).