Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extension Method makes LINQ break

Tags:

c#

.net

linq

I think I have hit a corner case of extension methods and LINQ.

Today I was declaring some extension methods to make my code more readable.So I created an extension method that gets an object and performs a direct cast:

public static class GeneralExtensions
{
    public static T Cast<T>(this object o)
    {
        return (T)o;
    }
}

The intention was to be able to call my direct castings by something like this:

MyObject.CastTo<MyInterface>();

It happens that in the same namespace I have an extension method that has a LINQ expression

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

public static class EnumExtenstions
{
    public static IEnumerable<string> UseLinq(this IEnumerable<object> collection)
    {
        return (from object value in collection select value.ToString() ).ToList();
    }
}

Adding this first extension method to my code base cause the next error

Error   CS1936  Could not find an implementation of the query pattern for source type 'object'.  'Select' not found.    

Having both extension methods in different namespaces (and not referred), or rename Cast to something different solves the issue.

I would like to understand a bit more why this is happening. Is it some overlap with LINQ? And if so why my Cast has priority?

Find the code in .NET Fiddle (Link)

like image 779
Kanekotic Avatar asked Dec 14 '22 09:12

Kanekotic


1 Answers

I would like to understand a bit more why this is happening. Is it some overlap with LINQ?

Yes!

When you have a query of the form

from Foo bar in blah select baz

this is rewritten by the compiler into a series of method calls:

blah.Cast<Foo>().Select(bar => baz)

The method calls are resolved just like ordinary method calls. If blah has a member Cast then it is used; if not, then extension methods are searched.

And if so why my Cast has priority?

The rules for resolving extension methods are a bit tricky but the basic rule is "closest containing class wins". If your extension methods are in a class in the global namespace, and the LINQ extension methods are in a class in the System.Linq namespace, then your extensions are closer. To get to your class the compiler had to go "up" from the current namespace to the global namespace. To get to System.Linq the compiler had to go "up" to the global namespace and then down into System.Linq to find the right class.

In particular, note that the C# compiler does not "backtrack". It does not say "well, the version of Cast that returns object gave me an error when I tried to use Select; is there another version of Cast that works?" The C# compiler simply says that the best possible version of Cast gives an error, and therefore it should not try to find a worse verison of Cast that works. In this particular case that's the behaviour you want, but in many cases you would end up getting an unexpected method called. C# prefers to give an error than to try to guess which method you really meant.

This is an oversimplification of the real rules, which can get complicated when there are multiple "using" directives in multiple nested namespaces. Consult the specification if you need the exact rules.

But better to not go there in the first place. Don't make your own extension methods named Cast, Select, Where, and so on, unless you intend to replicate the LINQ functionality in its entirity.


UPDATE: I just realized that I failed to address a potentially larger problem here: your cast method may not do what you want it to do, regardless of the fact that its naming conflicts with a method of the query pattern.

I note that your cast method is worse than just using a cast operator in a number of ways:

  • Cast<int>(123) unnecessarily boxes the int; (int)123 does not.
  • Cast<short>(123) fails but (short)123 succeeds. There is no conversion from a boxed int to a short.
  • Suppose you have a user-defined conversion from Animal to Shape. Cast<Shape>(new Tiger()) fails but (Shape) new Tiger() succeeds.
  • Suppose q is a nullable int that happens to be null. Cast<string>(q) succeeds! But (string)q would fail at compile time

And so on. Your cast method has some overlap with the real cast operator but it is by no means a substitute for it. If your intention is to capture the semantics of the cast operator then you need to use dynamic instead, which starts the compiler again at runtime and does the compile time analysis on the runtime types.

like image 176
Eric Lippert Avatar answered Dec 31 '22 02:12

Eric Lippert