Как проверить равенство перечислений Swift с соответствующими значениями
Я хочу проверить равенство двух значений Swift enum. Например:
enum SimpleToken {
case Name(String)
case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
XCTAssert(t1 == t2)
однако, компилятор не будет компилировать выражение равенства:
error: could not find an overload for '==' that accepts the supplied arguments
XCTAssert(t1 == t2)
^~~~~~~~~~~~~~~~~~~
должен ли я определить свою собственную перегрузку оператора равенства? Я надеялся, что компилятор Swift будет обрабатывать его автоматически, как это делают Scala и Ocaml.
11 ответов:
Как отмечали другие, Swift не синтезирует необходимые операторы равенства автоматически. Позвольте мне предложить более чистую (IMHO) реализацию, хотя:
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 } }это далеко от идеала - есть много повторений - но по крайней мере вам не нужно делать вложенные переключатели с if-операторами внутри.
реализация
Equatable- это перебор ИМХО. Представьте, что у вас сложное и большое перечисление со многими случаями и многими различными параметрами. Все эти параметры должны иметьEquatableосуществлены. Кроме того, кто сказал, что вы сравниваете случаи перечисления на основе "все или ничего"? Как насчет того, если вы тестируете значение и заглушили только один конкретный параметр перечисления? Я бы настоятельно предложил простой подход, например:if case .NotRecognized = error { // Success } else { XCTFail("wrong error") }... или в случае параметра оценка:
if case .Unauthorized401(_, let response, _) = networkError { XCTAssertEqual(response.statusCode, 401) } else { XCTFail("Unauthorized401 was expected") }найти более подробное описание здесь:https://mdcdeveloper.wordpress.com/2016/12/16/unit-testing-swift-enums/
похоже, что для перечислений и структур нет оператора равенства, сгенерированного компилятором.
" Если вы создаете свой собственный класс или структуру для представления сложной модели данных, например, то значение "равно" для этого класса или структуры не является тем, что Swift может угадать для вас."[1]
чтобы реализовать сравнение равенства, можно было бы написать что-то вроде:
@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] см. раздел "операторы эквивалентности" на https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_43
вот еще один вариант. Это в основном то же самое, что и другие, за исключением того, что он избегает вложенных операторов switch с помощью
if caseсинтаксис. Я думаю, что это делает его немного более читаемым(/терпимым) и имеет то преимущество, что он полностью избегает случая по умолчанию.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
Я использую этот простой обходной путь в модульном тестовом коде:
extension SimpleToken: Equatable {} func ==(lhs: SimpleToken, rhs: SimpleToken) -> Bool { return String(stringInterpolationSegment: lhs) == String(stringInterpolationSegment: rhs) }он использует строковую интерполяцию для выполнения сравнения. Я бы не рекомендовал его для производственного кода, но он лаконичен и выполняет работу по модульному тестированию.
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 } }
другой вариант-сравнить строковые представления случаев:
XCTAssert(String(t1) == String(t2))например:
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
другой подход с использованием
if caseс запятыми, который работает в 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 } }вот как я написал в своем проекте. Но я не могу вспомнить, откуда у меня появилась эта идея. (Я только что погуглил, но не видел такого использования.) Любые комментарии будут оценены.
t1 и t2 не являются числами, они являются экземплярами SimpleTokens со значениями, связанными.
можно сказать
var t1 = SimpleToken.Number(123)вы можете тогда сказать
t1 = SimpleToken.Name(“Smith”)без ошибок компилятора.
чтобы получить значение из t1, используйте оператор switch:
switch t1 { case let .Number(numValue): println("Number: \(numValue)") case let .Name(strValue): println("Name: \(strValue)") }
"преимущество" при сравнении с принятым ответом заключается в том, что в операторе "main" switch нет случая "по умолчанию", поэтому, если вы расширите свое перечисление с другими случаями, компилятор заставит вас обновить остальную часть кода.
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
вы можете сравнить с помощью switch
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") }
Comments