Cách kiểm tra tính bằng nhau của các enum Swift với các giá trị liên quan


192

Tôi muốn kiểm tra sự bằng nhau của hai giá trị Swift enum. Ví dụ:

enum SimpleToken {
    case Name(String)
    case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
XCTAssert(t1 == t2)

Tuy nhiên, trình biên dịch sẽ không biên dịch biểu thức đẳng thức:

error: could not find an overload for '==' that accepts the supplied arguments
    XCTAssert(t1 == t2)
    ^~~~~~~~~~~~~~~~~~~

Tôi có định nghĩa quá tải của riêng mình cho toán tử đẳng thức không? Tôi đã hy vọng trình biên dịch Swift sẽ tự động xử lý nó, giống như Scala và Ocaml.


1
Đã mở ndar: // 17408414 ( openradar.me/radar?id=6404186140835340 ).
Jay Lieske

1
Từ Swift 4.1 do SE-0185 , Swift cũng hỗ trợ tổng hợp EquatableHashablecho các enum có giá trị liên quan.
jedwidz

Câu trả lời:


244

Swift 4.1+

Như @jedwidz đã chỉ ra một cách hữu ích, từ Swift 4.1 (do SE-0185 , Swift cũng hỗ trợ tổng hợp EquatableHashablecho các enum với các giá trị liên quan.

Vì vậy, nếu bạn đang sử dụng Swift 4.1 hoặc mới hơn, phần sau đây sẽ tự động tổng hợp các phương thức cần thiết để XCTAssert(t1 == t2)hoạt động. Điều quan trọng là thêm Equatablegiao thức vào enum của bạn.

enum SimpleToken: Equatable {
    case Name(String)
    case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)

Trước Swift 4.1

Như những người khác đã lưu ý, Swift không tự động tổng hợp các toán tử đẳng thức cần thiết. Mặc dù vậy, hãy để tôi đề xuất triển khai (IMHO) sạch hơn:

enum SimpleToken: Equatable {
    case Name(String)
    case Number(Int)
}

public func ==(lhs: SimpleToken, rhs: SimpleToken) -> Bool {
    switch (lhs, rhs) {
    case let (.Name(a),   .Name(b)),
         let (.Number(a), .Number(b)):
      return a == b
    default:
      return false
    }
}

Thật xa vời với lý tưởng - có rất nhiều sự lặp lại - nhưng ít nhất bạn không cần phải thực hiện các chuyển đổi lồng nhau với câu lệnh if bên trong.


39
Điều hấp dẫn ở đây là bạn cần sử dụng câu lệnh mặc định trong khóa chuyển, vì vậy nếu bạn thêm trường hợp enum mới, trình biên dịch không đảm bảo bạn thêm mệnh đề để so sánh trường hợp mới đó cho bình đẳng - bạn sẽ chỉ cần nhớ và cẩn thận khi bạn thực hiện thay đổi sau này!
Thác Michael

20
Bạn có thể thoát khỏi vấn đề @MichaelWaterfall được đề cập bằng cách thay thế defaultbằng case (.Name, _): return false; case(.Number, _): return false.
Kazmasaurus

25
Tốt hơn: case (.Name(let a), .Name(let b)) : return a == bvv
Martin R

1
Với mệnh đề where, mỗi trường hợp sẽ tiếp tục được kiểm tra cho đến khi nó đạt mặc định cho mọi trường hợp false? Nó có thể là tầm thường nhưng loại điều đó có thể cộng lại trong một số hệ thống nhất định.
Christopher Swasey

1
Để làm việc này cả chức năng enum==chức năng phải được triển khai trên phạm vi toàn cầu (nằm ngoài phạm vi của bộ điều khiển xem của bạn).
Andrej

75

Triển khai Equatablelà một IMHO quá mức cần thiết. Hãy tưởng tượng bạn có enum phức tạp và lớn với nhiều trường hợp và nhiều thông số khác nhau. Những thông số này cũng sẽ phải được Equatablethực hiện. Hơn nữa, ai nói bạn so sánh các trường hợp enum trên cơ sở tất cả hoặc không có gì? Nếu bạn đang kiểm tra giá trị và chỉ khai thác một tham số enum cụ thể thì sao? Tôi mạnh mẽ đề nghị cách tiếp cận đơn giản, như:

if case .NotRecognized = error {
    // Success
} else {
    XCTFail("wrong error")
}

... hoặc trong trường hợp đánh giá tham số:

if case .Unauthorized401(_, let response, _) = networkError {
    XCTAssertEqual(response.statusCode, 401)
} else {
    XCTFail("Unauthorized401 was expected")
}

Tìm mô tả chi tiết hơn tại đây: https://mdcdeveloper.wordpress.com/2016/12/16/unit-testing-swift-enums/


Bạn có thể đưa ra một ví dụ đầy đủ hơn khi cố gắng sử dụng điều này không trong cơ sở thử nghiệm?
teradyl

Tôi không chắc câu hỏi ở đây là gì. if caseguard casechỉ đơn giản là các cấu trúc ngôn ngữ, bạn có thể sử dụng chúng ở bất cứ đâu khi kiểm tra tính bằng nhau của enum trong trường hợp này, không chỉ trong các Bài kiểm tra đơn vị.
mbpro

3
Mặc dù về mặt kỹ thuật, câu trả lời này không trả lời được câu hỏi, tôi nghi ngờ nó thực sự khiến nhiều người đến đây thông qua tìm kiếm, nhận ra rằng họ đã hỏi sai câu hỏi để bắt đầu. Cảm ơn!
Nikolay Suvandzhiev

15

Dường như không có trình biên dịch tạo toán tử đẳng thức cho enums, cũng như cho structs.

Ví dụ, nếu bạn tạo lớp hoặc cấu trúc của riêng mình để đại diện cho một mô hình dữ liệu phức tạp, thì ý nghĩa của hàm bằng với phạm vi của lớp đối với lớp hoặc cấu trúc đó không phải là thứ mà Swift có thể đoán cho bạn. [1]

Để thực hiện so sánh bình đẳng, người ta sẽ viết một cái gì đó như:

@infix func ==(a:SimpleToken, b:SimpleToken) -> Bool {
    switch(a) {

    case let .Name(sa):
        switch(b) {
        case let .Name(sb): return sa == sb
        default: return false
        }

    case let .Number(na):
        switch(b) {
        case let .Number(nb): return na == nb
        default: return false
        }
    }
}

[1] Xem "Toán tử tương đương" tại https://developer.apple.com/l Library / content / document / Swift / Contualual / Swift_Programming_Lingu / AdvifiedOperators.html # / // bitplef / doc / uid / TP400140


14

Đây là một lựa chọn khác. Nó chủ yếu giống như những cái khác ngoại trừ nó tránh các câu lệnh chuyển đổi lồng nhau bằng cách sử dụng if casecú pháp. Tôi nghĩ rằng điều này làm cho nó dễ đọc hơn một chút (/ có thể chịu được) và có lợi thế là tránh hoàn toàn trường hợp mặc định.

enum SimpleToken: Equatable {
    case Name(String)
    case Number(Int)
}
extension SimpleToken {
    func isEqual(st: SimpleToken)->Bool {
        switch self {
        case .Name(let v1): 
            if case .Name(let v2) = st where v1 == v2 { return true }
        case .Number(let i1): 
            if case .Number(let i2) = st where i1 == i2 { return true }
        }
        return false
    }
}

func ==(lhs: SimpleToken, rhs: SimpleToken)->Bool {
    return lhs.isEqual(rhs)
}

let t1 = SimpleToken.Number(1)
let t2 = SimpleToken.Number(2)
let t3 = SimpleToken.Name("a")
let t4 = SimpleToken.Name("b")

t1 == t1  // true
t1 == t2  // false
t3 == t3  // true
t3 == t4  // false
t1 == t3  // false

14
enum MyEnum {
    case None
    case Simple(text: String)
    case Advanced(x: Int, y: Int)
}

func ==(lhs: MyEnum, rhs: MyEnum) -> Bool {
    switch (lhs, rhs) {
    case (.None, .None):
        return true
    case let (.Simple(v0), .Simple(v1)):
        return v0 == v1
    case let (.Advanced(x0, y0), .Advanced(x1, y1)):
        return x0 == x1 && y0 == y1
    default:
        return false
    }
}

Điều này cũng có thể được viết với một cái gì đó giống như case (.Simple(let v0), .Simple(let v1)) Ngoài ra toán tử có thể staticở bên trong enum. Xem câu trả lời của tôi ở đây.
LShi

11

Tôi đang sử dụng cách giải quyết đơn giản này trong mã kiểm tra đơn vị:

extension SimpleToken: Equatable {}
func ==(lhs: SimpleToken, rhs: SimpleToken) -> Bool {
    return String(stringInterpolationSegment: lhs) == String(stringInterpolationSegment: rhs)
}

Nó sử dụng phép nội suy chuỗi để thực hiện so sánh. Tôi sẽ không đề xuất nó cho mã sản xuất, nhưng nó ngắn gọn và thực hiện công việc kiểm tra đơn vị.


2
Tôi đồng ý, đối với thử nghiệm đơn vị đây là một giải pháp tốt.
Daniel Wood

Tài liệu của Apple trên init (stringInterpolationSegment :) nói: "Đừng gọi trình khởi tạo này trực tiếp. Nó được trình biên dịch sử dụng khi diễn giải nội suy chuỗi." Chỉ cần sử dụng "\(lhs)" == "\(rhs)".
skagedal

Bạn cũng có thể sử dụng String(describing:...)hoặc tương đương "\(...)". Nhưng điều này không hoạt động nếu các giá trị liên quan khác nhau :(
Martin

10

Một tùy chọn khác là so sánh các biểu diễn chuỗi của các trường hợp:

XCTAssert(String(t1) == String(t2))

Ví dụ:

let t1 = SimpleToken.Number(123) // the string representation is "Number(123)"
let t2 = SimpleToken.Number(123)
let t3 = SimpleToken.Name("bob") // the string representation is "Name(\"bob\")"

String(t1) == String(t2) //true
String(t1) == String(t3) //false

3

Một cách tiếp cận khác sử dụng if casevới dấu phẩy, hoạt động trong Swift 3:

enum {
  case kindOne(String)
  case kindTwo(NSManagedObjectID)
  case kindThree(Int)

  static func ==(lhs: MyEnumType, rhs: MyEnumType) -> Bool {
    if case .kindOne(let l) = lhs,
        case .kindOne(let r) = rhs {
        return l == r
    }
    if case .kindTwo(let l) = lhs,
        case .kindTwo(let r) = rhs {
        return l == r
    }
    if case .kindThree(let l) = lhs,
        case .kindThree(let r) = rhs {
        return l == r
    }
    return false
  }
}

Đây là cách tôi đã viết trong dự án của tôi. Nhưng tôi không thể nhớ nơi tôi có ý tưởng. (Tôi vừa mới googled nhưng không thấy cách sử dụng như vậy.) Mọi bình luận sẽ được đánh giá cao.


2

t1 và t2 không phải là số, chúng là các thể hiện của SimpleTokens với các giá trị được liên kết.

Bạn có thể nói

var t1 = SimpleToken.Number(123)

Bạn có thể nói

t1 = SimpleToken.Name(Smith) 

không có lỗi biên dịch.

Để lấy giá trị từ t1, hãy sử dụng câu lệnh switch:

switch t1 {
    case let .Number(numValue):
        println("Number: \(numValue)")
    case let .Name(strValue):
        println("Name: \(strValue)")
}

2

'lợi thế' khi so sánh với câu trả lời được chấp nhận là, không có trường hợp 'mặc định' trong câu lệnh chuyển đổi 'chính', vì vậy nếu bạn mở rộng enum của mình với các trường hợp khác, trình biên dịch sẽ buộc bạn cập nhật phần còn lại của mã.

enum SimpleToken: Equatable {
    case Name(String)
    case Number(Int)
}
extension SimpleToken {
    func isEqual(st: SimpleToken)->Bool {
        switch self {
        case .Name(let v1):
            switch st {
            case .Name(let v2): return v1 == v2
            default: return false
            }
        case .Number(let i1):
            switch st {
            case .Number(let i2): return i1 == i2
            default: return false
            }
        }
    }
}


func ==(lhs: SimpleToken, rhs: SimpleToken)->Bool {
    return lhs.isEqual(rhs)
}

let t1 = SimpleToken.Number(1)
let t2 = SimpleToken.Number(2)
let t3 = SimpleToken.Name("a")
let t4 = SimpleToken.Name("b")

t1 == t1  // true
t1 == t2  // false
t3 == t3  // true
t3 == t4  // false
t1 == t3  // false

2

Mở rộng câu trả lời của mbpro, đây là cách tôi sử dụng phương pháp đó để kiểm tra sự bằng nhau của các enum nhanh với các giá trị liên quan với một số trường hợp cạnh.

Tất nhiên bạn có thể thực hiện một câu lệnh chuyển đổi, nhưng đôi khi thật tuyệt khi chỉ kiểm tra một giá trị trong một dòng. Bạn có thể làm như vậy:

// NOTE: there's only 1 equal (`=`) sign! Not the 2 (`==`) that you're used to for the equality operator
// 2nd NOTE: Your variable must come 2nd in the clause

if case .yourEnumCase(associatedValueIfNeeded) = yourEnumVariable {
  // success
}

Nếu bạn muốn so sánh 2 điều kiện trong cùng một mệnh đề if, bạn cần sử dụng dấu phẩy thay cho &&toán tử:

if someOtherCondition, case .yourEnumCase = yourEnumVariable {
  // success
}

2

Từ Swift 4.1, chỉ cần thêm Equatablegiao thức vào enum của bạn và sử dụng XCTAsserthoặc XCTAssertEqual:

enum SimpleToken : Equatable {
    case Name(String)
    case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
XCTAssertEqual(t1, t2) // OK

-1

Bạn có thể so sánh bằng cách sử dụng chuyển đổi

enum SimpleToken {
    case Name(String)
    case Number(Int)
}

let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)

switch(t1) {

case let .Number(a):
    switch(t2) {
        case let . Number(b):
            if a == b
            {
                println("Equal")
        }
        default:
            println("Not equal")
    }
default:
    println("No Match")
}

Nơi hoàn hảo cho một chuyển đổi với hai đối số. Xem ở trên làm thế nào điều này chỉ mất một dòng mã cho mỗi trường hợp. Và mã của bạn thất bại cho hai số không bằng nhau.
gnasher729
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.