Swift:在子类中覆盖 == 结果仅在超类中调用 ==

Swift: Overriding == in subclass results invocation of == in superclass only

我有一个classA,它符合Equatable协议,实现了==功能。在 subclass B 中,我用更多检查覆盖了 ==

但是,当我比较 B 的两个实例数组(它们都具有 Array<A> 类型)时,会调用 == for A。当然,如果我将两个数组的类型更改为 Array<B>,将调用 B==

我想出了以下解决方案:

A.swift:

internal func ==(lhs: A, rhs: A) -> Bool {
    if lhs is B && rhs is B {
        return lhs as! B == rhs as! B
    }
    return ...
}

这看起来真的很难看,必须为 A 的每个子 class 扩展。有没有办法确保首先调用 subclass 的 ==

为包含 BArray<A> 调用 A 的相等性的原因是自由函数的重载是静态解析的,而不是动态解析的——也就是说,在编译时基于类型的时间,而不是基于指向值的运行时。

这并不奇怪,因为 == 没有在 class 中声明,然后在子 class 中被覆盖。这可能看起来非常有限,但老实说,使用传统的 OO 技术定义多态相等性非常(并且看似)困难。有关详细信息,请参阅 this link and this paper

天真的解决方案可能是在 A 中定义一个动态调度函数,然后定义 == 来调用它:

class A: Equatable {
    func equalTo(rhs: A) -> Bool {
        // whatever equality means for two As
    }
}

func ==(lhs: A, rhs: A) -> Bool {
    return lhs.equalTo(rhs)
}

然后当你实现 B 时,你会覆盖 equalTo:

class B: A {
    override func equalTo(rhs: A) -> Bool {
        return (rhs as? B).map { b in
            return // whatever it means for two Bs to be equal
        } ?? false   // false, assuming a B and an A can’t be Equal
    }
}

你还得跳一个as?舞,因为你需要判断右手边的参数是不是B(如果equalTo跳了B直接,这不是合法的覆盖)。

这里还隐藏着一些可能令人惊讶的行为:

let x: [A] = [B()]
let y: [A] = [A()]

// this runs B’s equalTo
x == y
// this runs A’s equalTo
y == x

也就是说,参数的顺序改变了行为。这不好——人们期望平等是对称的。所以你真的需要上面链接中描述的一些技术来正确解决这个问题。

此时您可能会觉得所有这些都变得有点不必要了。它可能是,特别是考虑到 Swift 标准库中 Equatable 的文档中的以下评论:

Equality implies substitutability. When x == y, x and y are interchangeable in any code that only depends on their values.

Class instance identity as distinguished by triple-equals === is notably not part of an instance's value. Exposing other non-value aspects of Equatable types is discouraged, and any that are exposed should be explicitly pointed out in documentation.

鉴于此,如果您实现平等的方式 而不是 ,您可能会认真考虑重新考虑 Equatable 实现很高兴两个值相等,可以相互替换。避免这种情况的一种方法是将对象标识视为相等性的度量,并根据 === 实现 ==,对于 superclass 只需执行一次。或者,您可以问问自己,您真的 需要实现继承吗?如果不是,请考虑放弃它并改用值类型,然后使用协议和泛型来捕获您正在寻找的多态行为。

我 运行 遇到了类似的问题,因为我想在 MKPointAnnotation 子类(继承自 NSObject)上使用 difference(from:)。即使我将 func == 添加到我的注释子类中,difference(from: 仍然会调用 NSObject 的 == 实现。我相信这只是比较了 2 个对象的内存位置,这不是我想要的。为了使 difference(from: 正常工作,我必须为我的注释子类实现 override func isEqual(_ object: Any?) -> Bool {。在正文中,我会确保 object 与我的子类是同一类型,然后在那里进行比较。