【问题标题】:Operator overloading with generic function parameters带有泛型函数参数的运算符重载
【发布时间】:2014-10-07 03:21:59
【问题描述】:

我对泛型方法的运算符解析有疑问。

根据我对函数 EqualOperatorGeneric(下面的示例代码)中规范第 7.3.4 节的理解,应该找到类型 A 上 == 运算符的正确重载,但它似乎获得了 (object , 对象)。

我是不是在做一些非常明显的错误?有没有一种方法可以获得预期的行为,如果没有,我可以将给定的情况变成编译时或运行时错误吗?

public class A
{
    public A(int num)
    {
        this.Value = num;
    }

    public int Value { get; private set; }

    public override bool Equals(object obj)
    {
        var other = obj as A;
        if (Object.ReferenceEquals(other, null))
            return false;

        return Object.Equals(this.Value, other.Value);
    }

    public override int GetHashCode()
    {
        return this.Value.GetHashCode();
    }

    public static bool operator ==(A l, A r)
    {
        if (Object.ReferenceEquals(l, null))
        {
            return !Object.ReferenceEquals(r, null);
        }

        return l.Equals(r);
    }

    public static bool operator !=(A l, A r)
    {
        return !(l == r);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(EqualOperatorGeneric(new A(1), new A(1)));
    }

    public static bool EqualOperatorGeneric<L, R>(L l, R r)
        where L : class
        where R : class
    {
        return l == r;
    }
}

输出:

错误

【问题讨论】:

  • 为了清楚起见,您可以轻松地删除至少一半的代码并仍然演示相关原理。
  • 我个人喜欢 repros 有点冗长,但我可以删除其他平等检查。

标签: c# generics operator-overloading


【解决方案1】:

编译EqualOperatorGeneric 时,== 运算符需要在编译方法时静态绑定到单个实现。对于泛型方法的每个单独使用,它没有单独绑定。

这就是泛型与 C++ 模板的区别。泛型方法被编译一次,然后应用到每组类型参数的每次使用中,而模板是为每组泛型参数单独编译的。

【讨论】:

  • 这很有意义(从 C++ 模板视图中更多地考虑这一点)实现方式。但它不直接违背规范的措辞吗?这是我必须处理的一个令人难以置信的令人讨厌的错误的复制品。有什么方法可以在编译时检测到这种情况?
  • @MrDosu 不,它不违反规范。出于重载决策的目的,运算符的两个操作数的类型是objectobject,因为在编译泛型方法时,这些类型可以解析为任何类型的对象。至于检测它,你是什么意思?检测具有泛型类型操作数的泛型函数中运算符的使用?这听起来没什么用。您只会看到大量通用运算符的正确用法。
  • 我猜“确定 T 的类型”可以有多种解释方式,在这种情况下,编译一次泛型对象是合乎逻辑的答案。仍然务实地说,这意味着 == 运算符在泛型中根本被破坏了。
  • @MrDosu 不,它们并没有从根本上损坏,它们只是从根本上与您预期的不同。它们完全按照设计的方式工作。您期望的通用操作是动态编译的,但事实并非如此。你对泛型的基本理解是完全错误的。
  • 如果它们在特定场景中的工作方式根本不同,并在明确定义的类中改变相等的语义,我会务实地称之为破碎。从技术上讲,您可能会称其为该语言的一个美丽特性,您必须同意它在有用性范围内非常低。语义。
【解决方案2】:

在浏览了规范之后,我意识到您可以使用 dynamic 关键字将运算符的绑定从编译时推迟到运行时。这解决了我一直遇到的问题:

public static bool EqualOperatorGeneric<L, R>(L l, R r)
{
    dynamic dl = l, dr = r;
    return dl == dr;
}

【讨论】:

  • 我强烈建议您不要这样做。而不是使用专门设计为静态绑定的工具并将整个方法的编译的绑定推迟到运行时,您应该简单地使用一个本质上设计为在编译此泛型方法时不绑定的相等比较工具,特别是通过使用object.EqualsIEqualityComparer。除了每次调用方法都不需要重新编译之外,这将继续满足方法调用者的期望,因为它更符合 C# 的习惯。
  • 在现实世界中,比较工具的使用并不总是由为相关对象定义相等逻辑的参与者决定。对于在编译时无法确定正确的多态行为的情况,动态调度是在这里使用的正确工具。在将编程语言用作工具而不是玩具的情况下,您关于技术“纯粹性”的论点毫无意义。
  • 要比较的对象本身没有在庄园中定义相等的情况,这正是您所希望的 IEqualityComparer 的用途,以及为什么它听起来完全是这个特定工具的正确工具方法。您可以轻松地创建一个相等比较器,该比较器使用对象的 == 运算符来执行其相等性检查,如果这是您想要的语义并且对象的 Equals 方法具有不同的实现。话虽如此,对象的Equals 方法和== 运算符在相等语义上存在分歧通常是非常糟糕的做法。
  • 您似乎不了解最初的情况。要比较的对象定义了所需的相等性。静态绑定导致 .Equals 和 == 的语义不同。动态分派带来正确的(由对象设计者实现的含义)行为。你上面的评论毫无意义。
  • 假设每个 Type 都合理地定义了相等,也就是说 == 运算符和 Equals 方法为相同的对象返回相同的值(当变量的编译时类型与运行时类型相同),那么这实际上意味着在实际执行相等检查时,您应该使用Equals 进行后期绑定,== 进行早期绑定。您在调用方使用了错误的工具 == 而不是 Equals,因为您想要后期绑定。使用早期的边界相等工具==,并在后期编译整个方法,非常迂回。
猜你喜欢
  • 2017-04-28
  • 2017-01-04
  • 1970-01-01
  • 2014-01-01
  • 1970-01-01
  • 2020-02-07
  • 1970-01-01
  • 1970-01-01
  • 2016-01-25
相关资源
最近更新 更多