0

Consider the following code:

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

public static class Ex
{
    public static IEnumerable<T> Take<T>(this IEnumerable<T> source, long cnt)
    {
        return source;
    }
}

public class C 
{
    public static void Main() 
    {
        foreach(var e in Enumerable.Range(0, 10).Take(5).ToArray())
            Console.Write(e + " ");
    }
}

I have an extension on IEnumerable<T> for Take(long), which isn't provided by the framework. The framework only provides Take(int). And since I'm calling it with an int parameter (Take(5)), I would have expected it to use the framework version, but it's calling my extension.

Am I missing something? The closest match would obviously be the one that takes int as a parameter, and System.Linq is included so it should be in the pool of valid overloads. In fact if I delete my extension, the correct framework function is called.

For reference

Edit: Moving them to different namespaces shows the same problem:

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

namespace N1
{
    public static class Ex
    {
        public static IEnumerable<T> Take<T>(this IEnumerable<T> source, long cnt)
        {
            return source;
        }
    }
}

namespace N2
{
    using N1;
    public class C 
    {
        public static void Main() 
        {
            foreach(var e in Enumerable.Range(0, 10).Take(5).ToArray())
                Console.Write(e + " ");
        }
    }
}

For reference

Blindy
  • 65,249
  • 10
  • 91
  • 131
  • 1
    From reading documentation on the `linq` `.Take`, you shouldn't ever need to pull more than 2 billion items from the `IEnumerable`. Hence, the reason the framework doesn't provide a `.Take(long)`. – Ryan Wilson Apr 27 '20 at 15:28
  • Have you tried calling it with `5L` rather than `5` ? – Sean Apr 27 '20 at 15:29
  • 1
    I think because both your extension method and the caller are in the same namespace, that one wins. Can't remember the exact rules at the moment. – Damien_The_Unbeliever Apr 27 '20 at 15:30
  • @Sean, I have no problem calling the `long` version of `Take`, that's in fact my issue. – Blindy Apr 27 '20 at 15:33
  • 1
    @Blindy You're example has no real world application and would be considered poor design. Whether you asked for advice or not, I'm pointing out the fault in your example. – Ryan Wilson Apr 27 '20 at 15:36
  • Once the extension method is inside the same namespace it uses it. – Aviad Hasidof Apr 27 '20 at 15:37
  • @Damien_The_Unbeliever That doesn't seem to be the case either, I moved both blocks to different namespaces, and imported the extension -- same result: https://sharplab.io/#gist:3af31bc98c5fb687d71469a6c057622f – Blindy Apr 27 '20 at 15:39
  • Imagine you created that `Take` method before the other `Take` existed with your own logic and behaviour (like here behaving differently then the other `Take`) now suddenly a library also implements a `Take` extension but with an int, and suddenly when recompiling your own code is not called anymore since this new overload you might not know about has a "better" signature. Or atleast that is a scenario they're probably trying to prevent. Would very much feel like unexpected behaviour to me –  Apr 27 '20 at 15:57
  • @Blindy Most of the LINQ operations only support `int` for a reason, even when you get your overload working you will still struggle with other operations if your collection holds more than ~2 billion items – maccettura Apr 27 '20 at 17:12

2 Answers2

2

Because as Eric Lippert puts it:

the fundamental rule by which one potential overload is judged to be better than another for a given call site: closer is always better than farther away.

Closer is better

David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67
  • The last item on his list would apply to my second example, since the nested `using` is "closer" to my usage than the outside `System.Linq`. Indeed if I add `using System.Linq` in the nested namespace it works as expected. Weird, but I just asked for the reason, and you're right, it's this, thank you! – Blindy Apr 27 '20 at 16:01
0

Try System.Linq.Enumerable.Take(source, 5) instead of just Take(source, 5) to force using the original "Take" function or rename your own "Take" into somthing else "Takef" for example to avoid this kind of problems.

kiliz
  • 95
  • 4
  • It's an extension method, what you suggested makes no sense. – Blindy Apr 27 '20 at 15:32
  • I've already got this problem, I don't know why but it helped (I know that in normal cases there is non sense to do this, but if it work... ^^'7) – kiliz Apr 27 '20 at 15:49
  • 1
    You can call Extension methods as normal "Prefix" methods, instead of as "Infix" extension methods. So `System.Linq.Enumerable.Take(someCollection,5);` – David Browne - Microsoft Apr 27 '20 at 17:40