扩展方法使LINQ中断

时间:2016-01-28 16:25:08

标签: c# .net linq

我想我已经遇到了扩展方法和LINQ的角落。

今天我宣布了一些扩展方法,使我的代码更具可读性。所以我创建了一个获取对象并执行直接转换的扩展方法:

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

目的是能够通过以下方式调用我的直接铸件:  

MyObject.CastTo<MyInterface>();

碰巧在同一个命名空间中我有一个具有LINQ表达式的扩展方法

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();
    }
}

将第一个扩展方法添加到我的代码库会导致下一个错误

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

将两种扩展方法放在不同的名称空间(而不是引用),或将Cast重命名为不同的名称可以解决问题。

我想更多地了解为什么会发生这种情况。它与LINQ有些重叠吗?如果是这样,为什么Cast有优先权?

在.NET Fiddle(Link

中查找代码

1 个答案:

答案 0 :(得分:12)

  

我想更多地了解为什么会发生这种情况。它与LINQ有些重叠吗?

是!

查询表格时

from Foo bar in blah select baz

这由编译器重写为一系列方法调用:

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

方法调用就像普通方法调用一样被解析。如果blah有成员Cast则使用它;如果没有,则搜索扩展方法。

  

如果是这样,为什么我的演员有优先权?

解决扩展方法的规则有点棘手,但基本规则是“最接近包含类胜利”。如果您的扩展方法位于全局命名空间中的类中,并且LINQ扩展方法位于System.Linq命名空间中的类中,那么您的扩展名 close 。为了进入你的类,编译器必须从当前命名空间“向上”到全局命名空间。要进入System.Linq,编译器必须“向上”到全局命名空间,然后进入System.Linq以找到正确的类。

特别要注意,C#编译器不会“回溯”。它没有说“好吧,当我尝试使用Select时,返回对象的Cast的版本给了我一个错误;是否有另一个版本的Cast可以使用?” C#编译器只是说最好的Cast版本会产生错误,因此它不应该试图找到更糟糕的Cast版本。在这种特殊情况下,这是您想要的行为,但在许多情况下,您最终会得到一个名为的意外方法。 C#更喜欢给出错误,而不是试图猜测你真正想要的方法。

这是对实际规则的过度简化,当多个嵌套命名空间中存在多个“using”指令时,这些规则会变得复杂。如果您需要确切的规则,请参阅规范。

但最好不要首先去那里。除非您打算以其完整性复制LINQ功能,否则不要将自己的扩展方法命名为Cast,Select,Where等。

更新:我刚刚意识到我在这里没有解决一个可能更大的问题:你的强制转换方法可能无法完成你想要它做的事情,无论它的命名与查询模式的方法冲突。

我注意到你的强制转换方法比仅使用强制转换操作符更糟糕:

  • Cast<int>(123)不必要地插入int; (int)123没有。
  • Cast<short>(123)失败但(short)123成功。没有从盒装int转换为short。
  • 假设您拥有从AnimalShape的用户定义转化。 Cast<Shape>(new Tiger())失败但(Shape) new Tiger()成功。
  • 假设q是一个可空的int,恰好为null。 Cast<string>(q)成功!但是(string)q在编译时会失败

等等。你的演员方法与真正的演员操作员有一些重叠,但它绝不是它的替代品。如果您的目的是捕获转换操作符的语义,那么您需要使用dynamic代替它,它会在运行时再次启动编译器并对运行时类型进行编译时分析。