扩展方法使 LINQ 中断

Posted

技术标签:

【中文标题】扩展方法使 LINQ 中断【英文标题】:Extension Method makes LINQ break 【发布时间】:2016-01-28 16:25:07 【问题描述】:

我认为我遇到了扩展方法和 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) 中查找代码

【问题讨论】:

您的 Cast 方法隐藏了 Linq Cast 方法。尝试重命名它。 完全删除这个方法,没有必要,也很危险。实际上,在没有任何“帮助”的情况下,它甚至比使用 c# 语言需要更多的字符 @Chris 为什么这很危险?就我个人而言,我认为可读性的好处值得添加几个字符。 因为不清楚是 as 强制转换(安全)还是直接强制转换(危险)。一般来说,你不应该使用直接强制转换,它们是糟糕的软件设计的一个非常明确的指标。直接强制转换几乎纯粹用于违反 LSP en.wikipedia.org/wiki/Liskov_substitution_principle LSP 将讨论实例的替换,这不是强制转换的直接问题,因为这将更多地由使用接口驱动,而不是您使用的强制转换类型。我将捍卫通常应该按照预期类型使用直接强制转换,您不想用额外的逻辑来污染代码来测试空值。 'as' 应该只在你不确定类型并且你想继续你的工作流时才使用。 【参考方案1】:

我想进一步了解为什么会发生这种情况。它与 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 时,返回 object 的 Cast 版本给了我一个错误;还有另一个版本的 Cast 可以工作吗?” C# 编译器只是说最好的 Cast 版本会出错,因此它不应该尝试找到更差的 Cast 版本。在这种特殊情况下,这就是您想要的行为,但在许多情况下,您最终会调用一个意想不到的方法。 C# 更喜欢给出错误而不是试图猜测你真正想要的方法。

这是对真实规则的过度简化,当多个嵌套命名空间中有多个“使用”指令时,它会变得复杂。如果您需要确切的规则,请查阅规范。

但最好不要一开始就去那里。不要创建您自己的名为 Cast、Select、Where 等的扩展方法,除非您打算复制整个 LINQ 功能。


更新:我刚刚意识到我未能在这里解决一个潜在的更大问题:您的 cast 方法可能无法执行您希望它执行的操作,无论它的命名与查询模式的方法是否冲突。

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

Cast&lt;int&gt;(123) 不必要地将 int 装箱; (int)123 没有。 Cast&lt;short&gt;(123) 失败,但 (short)123 成功。没有从 boxed int 到 short 的转换。 假设您有一个用户定义的从AnimalShape 的转换。 Cast&lt;Shape&gt;(new Tiger()) 失败,但 (Shape) new Tiger() 成功。 假设q 是一个可以为空的int,它恰好为空。 Cast&lt;string&gt;(q) 成功!但是(string)q 会在编译时失败

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

【讨论】:

报错信息如何?根本不清楚,也没有对问题的根源提供任何有用的指示! @disklosr:错误消息是假设使用查询语法的开发人员正在尝试创建查询,并且实现查询模式的方法必须在范围内。这里的具体场景,查询模式的 one 方法在范围内,shadows 是正确的方法,坦率地说,当时我没有遇到这种情况我们正在设计错误消息。自从我们设计那个错误信息以来,这是我十年来第一次想到它。

以上是关于扩展方法使 LINQ 中断的主要内容,如果未能解决你的问题,请参考以下文章

扩展方法和Enumerable

LINQ中的扩展方法

创建 Where 查询的 LINQ 扩展方法

Linq 中 Enumerable.Zip 扩展方法有啥用?

动态 linq:创建生成 JSON 结果的扩展方法

“折叠”LINQ 扩展方法在哪里?