Ошибка компилятора при сравнении значений типа enum со связанными значениями?

class MyClass 
{
    enum MyEnum {
        case FirstCase
        case SecondCase(Int)
        case ThirdCase
    }

    var state:MyEnum!

    func myMethod ()
    {
        if state! == MyEnum.FirstCase {
            // Do something
        }
    }
}

Я получаю ошибку компилятора, указывающую на оператор if ::

Двоичный оператор '==' не может быть применен к двум операндам 'MyClass.MyEnum'

Если вместо этого я использую оператор switch, проблем не будет:

switch state! {
    // Also, why do I need `!` if state is already an 
    // implicitly unwrapped optional? Is it because optionals also 
    // are internally enums, and the compiler gets confused?

case .FirstCase:
    // do something...

default:
    // (do nothing)
    break
}

Однако оператор switch кажется слишком многословным: я просто хочу do something вместо .FirstCase и ничего другого. Оператор if имеет больше смысла.

Что происходит с перечислениями и == ?

EDIT: это очень странно. После выбора версии switch и перехода к другим (совершенно не связанным) частям моего кода, а также возвращения, версия с оператором if (сравнение принудительно-развернутого свойства с фиксированным регистром перечисления) компилируется без ошибок. Я могу только заключить, что это как-то связано с каким-то поврежденным кешем в парсере, который был очищен по пути.

EDIT 2 (спасибо @LeoDabus и @MartinR): кажется, что ошибка появляется, когда я устанавливаю связанное значение с другим регистром перечисления (не тем, с которым я сравниваю - в данном случае .SecondCase). Я до сих пор не понимаю, почему это вызывает именно эту ошибку компилятора («Невозможно использовать бинарный оператор '=='...») или что это значит.


person Nicolas Miari    schedule 06.11.2015    source источник
comment
вы забыли инициализировать состояние и принудительно разворачиваете его. вместо этого добавить защиту let state = state else { return }   -  person Leo Dabus    schedule 06.11.2015
comment
Это фиктивный код с измененными именами типов и переменных и опущенным большинством методов. В моем реальном коде переменная инициализируется. В любом случае, это должно быть проблемой во время выполнения. На самом деле я проверяю значение в viewDidLoad(), и компилятор не может знать, инициализировано оно или нет.   -  person Nicolas Miari    schedule 06.11.2015
comment
Какую версию Xcode вы используете? Я не получаю эту ошибку здесь   -  person Leo Dabus    schedule 06.11.2015
comment
как у меня это странно. Вы получаете ту же ошибку с моим кодом? Ваш код отлично компилируется здесь   -  person Leo Dabus    schedule 06.11.2015
comment
Это твой настоящий код? Как сказал Лео, он без проблем компилируется в Xcode 7 и 7.1. – Возможно, у вас есть перечисление с связанными значениями?   -  person Martin R    schedule 06.11.2015
comment
Нет, это не мой настоящий код (как я уже сказал выше). Теперь, когда вы упомянули об этом, было связанное значение с другим случаем перечисления (с тех пор я удалил его); но я все еще не понимаю, как это может вызвать ошибку компилятора.   -  person Nicolas Miari    schedule 06.11.2015
comment
@NicolasMiari: Is is имеет значение, потому что перечисления со связанными типами не имеют оператора == по умолчанию.   -  person Martin R    schedule 06.11.2015
comment
Ага, понятно; пропустил тот. Бесконечно благодарен. Однако сообщение об ошибке могло бы быть немного более явным в этом отношении...   -  person Nicolas Miari    schedule 06.11.2015
comment
@NicolasMiari: Другой вариант — привести ваше перечисление в соответствие с Equatable, как показано здесь: stackoverflow.com/a/25726677/59541   -  person Nate Cook    schedule 06.11.2015


Ответы (2)


Как вы сказали в комментарии, ваш тип перечисления на самом деле имеет связанные значения. В этом случае для типа перечисления нет оператора == по умолчанию.

Но вы можете использовать сопоставление с образцом даже в операторе if (начиная со Swift 2):

class MyClass {
    enum MyEnum {
        case FirstCase
        case SecondCase
        case ThirdCase(Int)
    }

    var state:MyEnum!

    func myMethod () {
        if case .FirstCase? = state {

        }
    }
}

Здесь .FirstCase? — это ярлык для .Some(MyEnum.FirstCase).

В вашем операторе switch state не разворачивается автоматически, даже если это неявно развернутый необязательный элемент (в противном случае вы не смогли бы сопоставить nil). Но здесь можно использовать тот же шаблон:

switch state {
case .FirstCase?:
    // do something...
default:
    break
}

Обновление: начиная с Swift 4.1 (Xcode 9.3) компилятор может синтезировать соответствие Equatable/Hashable для перечислений со связанными значениями (если все их типы являются Equatable/Hashable). Достаточно заявить о соответствии:

class MyClass {
    enum MyEnum: Equatable {
        case firstCase
        case secondCase
        case thirdCase(Int)
    }

    var state:MyEnum!

    func myMethod () {
        if state  == .firstCase {
            // ...
        }
    }
}
person Martin R    schedule 06.11.2015
comment
Большое тебе спасибо. В итоге мне не понадобилось связанное значение в моем текущем коде, поэтому оператор if работает так, как планировалось изначально, но я запомню этот шаблон для дальнейшего использования. - person Nicolas Miari; 06.11.2015
comment
@Martin R: Необязательно (EA) == EA // дает мне true в Swift 2 - person user3441734; 06.11.2015
comment
@ user3441734: Извините, я не понимаю, о чем вы спрашиваете. - person Martin R; 06.11.2015
comment
Любой способ сделать это в одну строку? Например. let isEqual: Bool = .FirstCase == state - person BallpointBen; 26.07.2017
comment
@BallpointBen: Конечно: let isEqual = { _ -> Bool in if case .FirstCase? = state { return true } else { return false } }() — это одна строка :) - person Martin R; 26.07.2017
comment
Из Swift 4.1 из-за SE-0185 Swift также поддерживает синтез Equatable и Hashable для перечислений со связанными значениями. - person jedwidz; 22.11.2018

person    schedule
comment
Что произойдет, если вместо первой развертки с помощью guard вы пропустите его и попытаетесь просто сравнить state! == MyEnum.FirstCase? - person Nicolas Miari; 06.11.2015
comment
@NicolasMiari ваш код здесь работает нормально, пока вы инициализируете свойство состояния - person Leo Dabus; 06.11.2015
comment
Повторяю: компилятор (статический анализатор?) не может узнать, инициализировано свойство или нет в том месте, где оно используется (viewDidLoad), и я принудительно разворачиваю его (что я в этом нет необходимости, поскольку для начала это неявно развернутый необязательный параметр). И ошибка компилятора, которую я получал, не имела ничего общего с инициализацией, а со сравнением двух перечислений одного и того же типа (?????) - person Nicolas Miari; 06.11.2015