我想我已经遇到了扩展方法和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)
中查找代码答案 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。Animal
到Shape
的用户定义转化。 Cast<Shape>(new Tiger())
失败但(Shape) new Tiger()
成功。q
是一个可空的int,恰好为null。 Cast<string>(q)
成功!但是(string)q
在编译时会失败等等。你的演员方法与真正的演员操作员有一些重叠,但它绝不是它的替代品。如果您的目的是捕获转换操作符的语义,那么您需要使用dynamic
代替它,它会在运行时再次启动编译器并对运行时类型进行编译时分析。