忽略关联值比较Swift枚举类型 - 通用实现

6

我在项目中有几个不同的枚举类型,它们符合同一协议。该协议中的compareEnumType方法比较枚举类型时会忽略相关值。以下是来自Playground的我的代码:

protocol EquatableEnumType {
    static func compareEnumType(lhs: Self, rhs: Self) -> Bool
}

enum MyEnum: EquatableEnumType {
    case A(Int)
    case B

    static func compareEnumType(lhs: MyEnum, rhs: MyEnum) -> Bool {
        switch (lhs, rhs) {
        case (.A, .A): return true
        case (.B, .B): return true
        default: return false
        }
    }
}

enum MyEnum2: EquatableEnumType {
    case X(String)
    case Y

    static func compareEnumType(lhs: MyEnum2, rhs: MyEnum2) -> Bool {
        switch (lhs, rhs) {
        case (.X, .X): return true
        case (.Y, .Y): return true
        default: return false
        }
    }
}

let a = MyEnum.A(5)
let a1 = MyEnum.A(3)
if MyEnum.compareEnumType(lhs: a, rhs: a1) {
    print("equal") // -> true, prints "equal"
}

let x = MyEnum2.X("table")
let x1 = MyEnum2.X("chair")
if MyEnum2.compareEnumType(lhs: x, rhs: x1) {
    print("equal2") // -> true, prints "equal2"
}

在我的真实项目中,我有超过2个枚举类型,对于每个枚举类型,我都需要有类似实现compareEnumType函数的方法。
问题是:是否可能有一个通用的compareEnumType实现,它可以适用于符合EquatableEnumType协议的所有枚举类型?
我尝试在协议扩展中编写默认实现,如下所示:
extension EquatableEnumType {
    static func compareEnumType(lhs: Self, rhs: Self) -> Bool {
        // how to implement???
    }
}

但是我在实现过程中卡住了。我不知道如何访问lhsrhs中包含的值。有人能帮帮我吗?


这违反了Equatable的意义,它“暗示了可互换性 - 任何比较相等的两个实例都可以在依赖于它们的值的任何代码中互换使用。为了保持可互换性,== 运算符应该考虑 Equatable 类型的所有可见方面。不鼓励公开 Equatable 类型的非值方面,除了类身份之外的任何非值方面应该在文档中明确指出。” 这是一个非常糟糕的主意,你应该重新设计你的类型以避免它。如果你下决心要做这个,你需要的工具是 SwiftGen。 - Rob Napier
如果您的枚举类型携带着与相等无关的私有信息(例如缓存),则几乎肯定应该在此处使用类或结构体。枚举类型不适用于该问题。(任何基于枚举类型的解决方案都可以转换为基于协议+结构体的等效方案,反之亦然。维护的折衷会有所不同,但是逻辑总是可以实现的,因为它们是对偶的。) - Rob Napier
2
@RobNapier 我不想改变“==”的含义或实现。我知道它考虑到相关值,这是正确的。在我的协议中,我只想比较枚举情况,忽略相关值。我尝试使用“==”,但对我来说不起作用(这是正确的)。我正在寻找另一种解决方案,以便以通用方式比较枚举情况并忽略相关值。 - Anastasia
更新的问题:删除了关于Equatable协议的误导细节。 - Anastasia
1
当然。Sourcery。https://github.com/krzysztofzablocki/Sourcery(之前说了SwiftGen,抱歉;那是Sourcery基于的低级库,但对于这个问题,您需要Sourcery)简短和长久的答案是,您需要一定程度的元编程,而Swift中不存在这种元编程,这就是Sourcery目前填补这一空白的地方。总有一天,Swift会有更多的元编程,以至于Sourcery是不必要的,但今天还没有到那一天。 - Rob Napier
1
(通过您的编辑,这段内容没有任何问题,而且非常合理。只是将其与Equatable联系起来是个问题。) - Rob Napier
2个回答

2

很简单!我会使用一个实例方法,但如果你真的需要它是静态的,你可以将其重写为类函数。

原始答案: Original Answer

extension EquatableEnumCase {
    func isSameCase(as other: Self) -> Bool {
        let mirrorSelf = Mirror(reflecting: self)
        let mirrorOther = Mirror(reflecting: other)
        if let caseSelf = mirrorSelf.children.first?.label, let caseOther = mirrorOther.children.first?.label {
            return (caseSelf == caseOther) //Avoid nil comparation, because (nil == nil) returns true
        } else { return false}
    }
} 


enum MyEnum1: EquatableEnumCase {
    case A(Int)
    case B
}

enum MyEnum2: EquatableEnumCase {
    case X(String)
    case Y
}

let a = MyEnum1.A(5)
let a1 = MyEnum1.A(3)
if a.isSameCase(as: a1) {
    print("equal") // -> true, prints "equal1"
}

let x = MyEnum2.X("table")
let x1 = MyEnum2.X("chair")

if x.isSameCase(as: x1) {
    print("equal2") // -> true, prints "equal2"
}

let y = MyEnum2.Y
print(x.isSameCase(as: y) ? "equal3": "not equal3") // -> false, "not equal3"

0

我认为你无法自动生成这个,因此这里有一种使用扩展的方法。 我建议创建一个新的enum CompareEnumMethod,它告诉您是否要比较相关的values或仅比较type。 在您的协议中创建一个新函数compareEnum,该函数具有此枚举作为参数。 然后,您可以创建==(lhs:,rhs:)的扩展,并说它使用.value,对于compareEnumType您使用.type。 现在,只需要在每个枚举中实现compareEnum方法。

enum CompareEnumMethod {
    case type, value
}

protocol EquatableEnumType: Equatable {
    static func compareEnumType(lhs: Self, rhs: Self) -> Bool
    static func compareEnum(lhs: Self, rhs: Self, method: CompareEnumMethod) -> Bool
}

extension EquatableEnumType {
    static func compareEnumType(lhs: Self, rhs: Self) -> Bool {
        return Self.compareEnum(lhs: lhs, rhs: rhs, method: .type)
    }

    static func ==(lhs: Self, rhs: Self) -> Bool {
        return Self.compareEnum(lhs: lhs, rhs: rhs, method: .value)
    }
}

enum MyEnum: EquatableEnumType {
    case A(Int)
    case B

    static func compareEnum(lhs: MyEnum, rhs: MyEnum, method: CompareEnumMethod) -> Bool {
        switch (lhs, rhs, method) {
        case let (.A(lhsA), .A(rhsA), .value):
            return lhsA == rhsA
        case (.A, .A, .type),
             (.B, .B, _):
            return true
        default:
            return false
        }
    }
}

let a0 = MyEnum.A(5)
let a1 = MyEnum.A(3)
let b0 = MyEnum.B
print(MyEnum.compareEnumType(lhs: a0, rhs: a1)) //true
print(a0 == a1) //false
print(MyEnum.compareEnumType(lhs: a0, rhs: b0)) //false

谢谢您的回答!但是这种方式compareEnum()应该对每个枚举类型都有不同的实现(例如,在我的代码片段中,对于MyEnum2)。这就是我想避免的。我希望所有符合EquatableEnumType的枚举类型都有一个单一的实现。 - Anastasia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接