5

I read from https://msdn.microsoft.com/en-us/library/vstudio/bb383977.aspx that extension methods with same name and signature as existing ones on the base type are never called, but what about "overriding" an extension-method itself:

using System;
using System.Linq;
using System.Collections.Generic;


namespace ConsoleApplication1
{

    public class Program
    {

        public static void Main(string[] args)
        {

            var query = (new[] { "Hans", "Birgit" }).Where(x => x == "Hans");
            foreach (var o in query) Console.WriteLine(o);
        }

    }


    public static class MyClass
    {
        public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
        {
            foreach (var obj in source)
                if (predicate(obj)) yield return obj;
        }
    };

}

When I debug this program I DO run into my own extension-method rather then that one provided by System.Linq (although the namespace is included). Did I miss anything or is there also a priority for extension-methods?

MakePeaceGreatAgain
  • 35,491
  • 6
  • 60
  • 111
  • You do not show your `using` directives (needed for `IEnumerable<>` to be in scope), or whether you declare a `namespace`. This could be relevant. For example, are you writing your code inside `namespace System.Linq { }`? Is your `using` directive to `System.Linq` placed inside or outside your `namespace`? – Jeppe Stig Nielsen Feb 24 '15 at 14:57
  • @JeppeStigNielsen added namespace – MakePeaceGreatAgain Feb 24 '15 at 15:02
  • 2
    In the above (edited) code, namespace `ConsoleApplication1` is searched first, then the global (`null`) namespace, and then `System`, `System.Linq`, and `System.Collections.Generic`. Since `MyClass` is in namespace `ConsoleApplication1`, it is found first. – Jeppe Stig Nielsen Feb 24 '15 at 15:06

2 Answers2

16

When the compiler searches for extension methods, it starts with those declared in classes in the same namespace as the calling code, then works outwards until it reaches the global namespace. So if your code is in namespace Foo.Bar.Baz, it will first search Foo.Bar.Baz, then Foo.Bar, then Foo, then the global namespace. It will stop as soon as it finds any eligible extension methods. If it finds multiple eligible extension methods in the same step, and none is "better" than the other (using normal overloading rules) then you'll get a compile-time error for ambiguity.

Then (if it hasn't found anything) it considers extension methods imported by using directives. So, if you move your extension method to a different namespace which is unrelated to yours, you'll either get a compile-time error due to ambiguity (if you imported the namespace with a using directive) or it will only find the System.Linq method (if you didn't import the namespace containing your method).

This is specified in section 7.6.5.2 of the C# specification.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 2
    This depends on whether the `using` directives are places inside or outside the `namespace` declarations. – Jeppe Stig Nielsen Feb 24 '15 at 14:55
  • Thanks Jon, that clearifies the issue. However I also assumed that when having multiple solutions for the extension I´d get a compiler-error (not tested, but thanks @xanatos for pointing it out). – MakePeaceGreatAgain Feb 24 '15 at 14:56
  • @xanatos: I can't reproduce that. What exactly do you mean? (I tried declaring one extension method in the global namespace and one in `X`, then invoked it from a class in `X.Foo`; the one in `X` was invoked.) – Jon Skeet Feb 24 '15 at 14:56
  • @xanatos: Right, that's the ambiguity example I include in my answer, due to two different `using` directives. That's *not* the same as the "starting with the namespace containing the calling code and working outwards" step. – Jon Skeet Feb 24 '15 at 14:59
  • Jon, It will also be an ambiguity when two or more extension method found in the same namespace. – Sriram Sakthivel Feb 24 '15 at 15:02
  • @SriramSakthivel: Yes, that's true - but it isn't what xanatos had originally said :) – Jon Skeet Feb 24 '15 at 15:02
5

Here is a longer example with five possible Where<> methods:

using System;
using System.Collections.Generic;
using System.Linq; // **OUTSIDE**

namespace Me.MyName
{
  using System.MySuperLinq; // **INSIDE**

  static class Test
  {
    static void Main()
    {
      (new[] { "Hans", "Birgit" }).Where(x => x == "Hans");
    }
  }
}

namespace System.MySuperLinq
{
  static class Extensions
  {
    public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
    {
      Console.WriteLine("My own MySuperLinq method");
      return null; // will fix one day
    }
  }
}

namespace Me.MyName
{
  static class Extensions
  {
    public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
    {
      Console.WriteLine("My own MyName method");
      return null; // will fix one day
    }
  }
}

namespace Me
{
  static class Extensions
  {
    public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
    {
      Console.WriteLine("My own Me method");
      return null; // will fix one day
    }
  }
}

// this is not inside any namespace declaration
static class Extensions
{
  public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
  {
    Console.WriteLine("My own global-namespace method");
    return null; // will fix one day
  }
}

Try successively removing which ever method is preferred, to see the "priority" the C# compiler uses.

  • The C# compiler will first search for static classes inside Me.MyName namespace since that is the "current" namespace.
  • It will then search in System.MySuperLinq because that using is inside.
  • It will then go out one level and search in Me namespace
  • It will then search in the global (null) namespace.
  • Finally, it will serach System, System.Collections.Generic, and System.Linq.

If two or more equally relevant methods are found at one "level" (bullets above), that is a compile-time ambiguity (will not compile).

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181