Compiler error when comparing values of enum type with associated values?
As you said in a comment, your enumeration type actually has associated
values. In that case there is no default ==
operator for the enum type.
But you can use pattern matching even in an if
statement (since Swift 2):
class MyClass {
enum MyEnum {
case FirstCase
case SecondCase
case ThirdCase(Int)
}
var state:MyEnum!
func myMethod () {
if case .FirstCase? = state {
}
}
}
Here .FirstCase?
is a shortcut for .Some(MyEnum.FirstCase)
.
In your switch-statement, state
is not automatically unwrapped,
even if it is an implicitly unwrapped optional (otherwise you could
not match against nil
). But the same pattern can be used here:
switch state {
case .FirstCase?:
// do something...
default:
break
}
Update: As of Swift 4.1 (Xcode 9.3) the compiler can synthesize conformance to Equatable/Hashable for enums with associated values (if all their types are Equatable/Hashable). It suffices to declare the conformance:
class MyClass {
enum MyEnum: Equatable {
case firstCase
case secondCase
case thirdCase(Int)
}
var state:MyEnum!
func myMethod () {
if state == .firstCase {
// ...
}
}
}
How to do an if-else comparison on enums with arguments
The trick is to not actually check with == but rather use the case
keyword in conjunction with a single = in your if statement. This is a little counter intuitive in the beginning but just like if let
, you get used to it pretty fast:
enum Normal {
case one
case two, three
}
enum NormalRaw: Int {
case one = 1
case two, three
}
enum NormalArg {
case one(Int)
case two, three
}
let normalOne = Normal.one
let normalRawOne = NormalRaw.one
let normalArgOne = NormalArg.one(1)
if case .one = normalOne {
print("A normal one") //prints "A normal one"
}
if case .one = normalRawOne {
print("A normal \(normalRawOne.rawValue)") //prints "A normal 1"
}
if case .one(let value) = normalArgOne {
print("A normal \(value)") //prints "A normal 1"
}
The point is that in Swift you only get equation of enums for free if your enum uses a raw type or if you have no associated values (try it out, you can't have both at the same time). Swift however does not know how to compare cases with associated values - I mean how could it? Let's look at this example:
Normal.one == .one //true
Normal.one == .two //false
NormalRaw.one == .one //true
NormalRaw.one == .two //false
NormalArg.one(1) == .one(1) //Well...?
NormalArg.one(2) == .one(1) //Well...?
NormalArg.one(1) == .two //Well...?
Maybe this makes it clearer why this cannot work out of the box:
class Special {
var name: String?
var special: Special?
}
enum SpecialEnum {
case one(Special)
case two
}
var special1 = Special()
special1.name = "Hello"
var special2 = Special()
special2.name = "World"
special2.special = special1
SpecialEnum.one(special1) == SpecialEnum.one(special2) //Well...?
So if you want enums with associated values, you'll have to implement Equatable protocol in your enum by yourself:
enum NormalArg: Equatable {
case one(Int)
case two
static func ==(lhs: NormalArg, rhs: NormalArg) -> Bool {
switch (lhs, rhs) {
case (let .one(a1), let .one(a2)):
return a1 == a2
case (.two,.two):
return true
default:
return false
}
}
}
Comparing value of enum gives compiler error
Getting rid of all the Qt obfuscation, what's left is roughly this:
class Foo {
public:
enum fooValues { one, two };
};
void user(const Foo& foo) {
if (foo.fooValues == Foo::one) {
// doesn't compile
}
}
And the problem here is that Foo::fooValues
is a nested type, not a member variable. You never declare a member variable of that type.
You have been slightly mislead by the linked answers, because they don't do that either. But they don't try to use it this way either. Look at the linked question instead about what you need to do.
How to compare enum with associated values by ignoring its associated value in Swift?
Edit: As Etan points out, you can omit the (_)
wildcard match to use this more cleanly:
let number = CardRank.Number(5)
if case .Number = number {
// Is a number
} else {
// Something else
}
Unfortunately, I don't believe that there's an easier way than your switch
approach in Swift 1.2.
In Swift 2, however, you can use the new if-case
pattern match:
let number = CardRank.Number(5)
if case .Number(_) = number {
// Is a number
} else {
// Something else
}
If you're looking to avoid verbosity, you might consider adding an isNumber
computed property to your enum that implements your switch statement.
Comparing two enum variables regardless of their associated values
Updated approach:
I think there's no native support for this. But you can achieve it by defining a custom operator (preferrably by using a protocol, but you can do it directly as well). Something like this:
protocol EnumTypeEquatable {
static func ~=(lhs: Self, rhs: Self) -> Bool
}
extension DataType: EnumTypeEquatable {
static func ~=(lhs: DataType, rhs: DataType) -> Bool {
switch (lhs, rhs) {
case (.one, .one),
(.two, .two):
return true
default:
return false
}
}
}
And then use it like:
let isTypeEqual = DataType.One(value: 1) ~= DataType.One(value: 2)
print (isTypeEqual) // true
Old approach:
protocol EnumTypeEquatable {
var enumCaseIdentifier: String { get }
}
extension DataType: EnumTypeEquatable {
var enumCaseIdentifier: String {
switch self {
case .one: return "ONE"
case .two: return "TWO"
}
}
}
func ~=<T>(lhs: T, rhs: T) -> Bool where T: EnumTypeEquatable {
return lhs.enumCaseIdentifier == rhs.enumCaseIdentifier
}
The older version depends on Runtime and might be provided with default enumCaseIdentifier
implementation depending on String(describing: self)
which is not recommended. (since String(describing: self)
is working with CustromStringConvertible
protocol and can be altered)
How to test equality of Swift enums with associated values
Swift 4.1+
As @jedwidz has helpfully pointed out, from Swift 4.1 (due to SE-0185, Swift also supports synthesizing Equatable
and Hashable
for enums with associated values.
So if you're on Swift 4.1 or newer, the following will automatically synthesize the necessary methods such that XCTAssert(t1 == t2)
works. The key is to add the Equatable
protocol to your enum.
enum SimpleToken: Equatable {
case Name(String)
case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
Before Swift 4.1
As others have noted, Swift doesn't synthesize the necessary equality operators automatically. Let me propose a cleaner (IMHO) implementation, though:
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
}
}
It's far from ideal — there's a lot of repetition — but at least you don't need to do nested switches with if-statements inside.
Did not get compile error when comparing Enum with const int using Equals
!=
is a language convention so this is C# specific. Calling this operator is early bound, in other words, it will occur at compile time.
Equals
is a framework convention, .NET in this case, and that is bound at runtime.
When you call !=
, the decision is made by the C# compiler during compilation so you get an error. When you call Equals
the decision is made by the framework at runtime. Since your enum is not of type object, it will be turned into an object (boxing) and then the runtime will check if your type has overridden the Equals
method, since you have not, it will use the default implementation.
Equals
for Reference type
If the instance is a reference type, the default implementation of Equals
checks if one object reference is the same as the other object reference. If they are the same reference, it returns true. Otherwise it returns false.
Equals
for Value type
If the instance is a value type, then it will test for value equality. This is your case. It will check if the enum value you have is equal to the constant value. No error will be shown or thrown: it either equals or it does not.
Why do I get an error when directly comparing two enums?
This isn't a warning because of a standards compliance issue, it's one of those "this doesn't seem right" kind of warnings. If you think of the typical uses of enums, doing such a comparison doesn't make much sense in many cases. For example:
enum Day {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
};
enum Month {
January,
February,
March,
April,
May,
June,
July,
August,
September,
October,
November,
December
};
enum Day day = Wednesday;
enum Month month = April;
if (day == month) { ... }
This evaluates to true, but in general the comparison wouldn't make much sense. If you know you meant it, the typecast will convince the compiler, as you noted.
Testing for enum value fails if one has associated value?
Enumerations are automatically Equatable
when they have a raw value that's Equatable
. In your first case, the raw value is assumed to be Int
, but it would work if you'd given it another specific type like UInt32
or even String
.
Once you add an associated value, however, this automatic conformance with Equatable
doesn't happen any more, since you can declare this:
let littleNorth = CompassPoint.North(2)
let bigNorth = CompassPoint.North(99999)
Are those equal? How should Swift know? You have to tell it, by declaring the enum
as Equatable
and then implementing the ==
operator:
enum CompassPoint : Equatable {
case North(Int)
case South
case East
case West
}
public func ==(lhs:CompassPoint, rhs:CompassPoint) -> Bool {
switch (lhs, rhs) {
case (.North(let lhsNum), .North(let rhsNum)):
return lhsNum == rhsNum
case (.South, .South): return true
case (.East, .East): return true
case (.West, .West): return true
default: return false
}
}
Now you can test for equality or inequality, like this:
let otherNorth = CompassPoint.North(2)
println(littleNorth == bigNorth) // false
println(littleNorth == otherNorth) // true
Related Topics
Swift5 Macos Imageresize Memory Issue
In Xcode 6.1. 'Uiimage' Does Not Have a Member Named 'Size' Error
How to Include .Swift File from Other .Swift File in an Immediate Mode
Swift Repl: How to Import, Load, Evaluate, or Require a .Swift File
Is Swift Dictionary of Indexed for Performance? Even for Exotic Types (Uuid)
Swift Make Method Parameter Mutable
How to Require That a Protocol Can Only Be Adopted by a Specific Class
How to Move to Next Textfield in Swiftui
Adding 3D Object to Argeoanchor
Swift 2.1 [Uint8] --Utf8--> String
Access Input from Uialertcontroller
Swift 2.0: Protocol Extensions: Two Protocols with the Same Function Signature Compile Error
Why Does Implicitly Unwrapped Optional Not Unwrap in Dictionary of Type [String:Any]