扩展方法使 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 中断

我想更多地了解为什么会发生这种情况。它是否与 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 的转换。
  • 假设您有一个从 AnimalShape 的用户定义转换。 Cast<Shape>(new Tiger())失败了,但(Shape) new Tiger()成功了。
  • 假设 q 是一个可为空的 int,恰好为 null。 Cast<string>(q)成功了! 但是(string)q在编译时会失败

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