4

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)

Kanekotic
  • 2,824
  • 3
  • 21
  • 35
  • Your `Cast` method is hiding the Linq `Cast` method. Try renaming it. – juharr Jan 28 '16 at 16:31
  • Just delete this method entirely, it is unnecessary and dangerous. It actually takes more characters to even use than the c# language without any "help" – Chris Marisic Jan 28 '16 at 16:47
  • @Chris why is this dangerous? And personally I think the readability benefit is worth the few added characters. – disklosr Jan 28 '16 at 17:09
  • Because it's not apparent whether it's an `as` cast (safe) or direct cast (dangerous). In general you shouldn't be using direct casts, they are a very clear indicator of bad software design. Direct casts are almost purely used to violate the LSP https://en.wikipedia.org/wiki/Liskov_substitution_principle – Chris Marisic Jan 28 '16 at 17:57
  • LSP will talk about the replacement of instances this is not a direct concern of casting because that will be more driven by using interfaces than the type of cast you use. I will defend that direct casts should normally been used as per is an expected type, you don't want to pollute the code with extra logic to test nulls. 'as' should only be used to cast when you are not sure of the type and you want to continue your workflow. – Kanekotic Jan 28 '16 at 19:43
  • @ChrisMarisic: I agree with your sentiment, but not entirely with your reasoning. Supposing we limit ourselves to discussing representation-preserving conversions on reference types. Both "as" and "direct" casts are the developer telling the compiler "I know something you don't, so trust me on this one". Any time I have to tell the compiler that I know more about the type system than it does, I worry that I might be wrong! Either kind of conversion indicates that the program is so complex that it is not *provably type safe* by the compiler. – Eric Lippert Jan 28 '16 at 20:13
  • @EricLippert i agree, nevertheless there are times when you want to abandon type safety (indeed very "advanced"/complex). There's also lots of value of `as` for things like pulling data out of a sql reader http://stackoverflow.com/a/2433275/37055 i could always add more qualifiers to statements. Digressing, OP definitely isn't querying about complex dynamic programming and loose typing, hence my original statement. – Chris Marisic Jan 28 '16 at 20:21
  • @ChrisMarisic I think you have good points, and I completely agree with EricLippert. I think we could agree there is an unspoken rule based on the certainty of the cast and the severity of the failure to cast. I think this enters the never ending debate of 'handle' or 'break', and at what level. In the specific of the systems that I work my certainty in types is high and if somethings fails to cast is an issue, so the process has to stop at that point, there is no need for me to pollute the code with more statements if i am not affected by the performance implications of throwing an exception. – Kanekotic Jan 29 '16 at 06:59

1 Answers1

12

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.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • How about the error message? It's not clear at all and doesn't give any helpful indication to the source of the problem! – disklosr Jan 28 '16 at 17:15
  • @disklosr: The error message was written assuming that a developer who uses the query syntax is attempting to create a query, and that methods which implement the query pattern must be in scope. The specific scenario here, where *one* method of the query pattern is in scope and *shadows* the correct method, well to be perfectly frank that scenario did not occur to me at the time we were designing the error messages. This is the first time I've thought of it in the ten years since we designed that error message. – Eric Lippert Jan 28 '16 at 17:27