扩展方法使 LINQ 中断
本文关键字:中断 LINQ 方法 扩展 | 更新日期: 2023-09-27 18:32:15
我想我已经遇到了扩展方法和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 小提琴中查找代码(链接)
我想更多地了解为什么会发生这种情况。它是否与 LINQ 有些重叠?
是的!
当您有表单查询时
from Foo bar in blah select baz
编译器将其重写为一系列方法调用:
blah.Cast<Foo>().Select(bar => baz)
方法调用的解析方式与普通方法调用一样。如果 blah 有一个成员Cast
则使用它;如果不是,则搜索扩展方法。
如果是这样,为什么我的演员优先?
解析扩展方法的规则有点棘手,但基本规则是"最接近包含类获胜"。 如果扩展方法位于全局命名空间中的类中,并且 LINQ 扩展方法位于 System.Linq 命名空间中的类中,则扩展更近。 为了访问你的类,编译器必须从当前命名空间"向上"到全局命名空间。要访问 System.Linq,编译器必须"向上"到全局命名空间,然后向下进入 System.Linq 以找到正确的类。
特别要注意的是,C# 编译器不会"回溯"。它没有说"好吧,当我尝试使用 Select 时,返回对象的 Cast 版本给了我一个错误;有没有另一个版本的演员表有效?C# 编译器只是说最好的 Cast 版本会给出错误,因此它不应该尝试找到一个更糟糕的 Cast 版本。在这种特殊情况下,这是您想要的行为,但在许多情况下,您最终会调用一个意外的方法。C# 更喜欢给出错误,而不是尝试猜测您真正意味着哪种方法。
这是对实际规则的过度简化,当多个嵌套命名空间中有多个"using"指令时,规则可能会变得复杂。如果您需要确切的规则,请查阅规范。
但最好一开始就不要去那里。不要创建自己的名为"强制转换"、"选择"、"位置"等的扩展方法,除非您打算完整地复制 LINQ 功能。
更新:我
刚刚意识到我未能在这里解决一个潜在的更大问题:您的强制转换方法可能不会执行您希望它执行的操作,而不管其命名与查询模式的方法冲突的事实。
我注意到您的强制转换方法在许多方面比仅使用转换运算符更糟糕:
-
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
,这会在运行时再次启动编译器并对运行时类型执行编译时分析。