J'ai cette énumération:
if [Foo.a, Foo.b, Foo.c].contains(foo) { ... }
et foo
if case .a, .b, .c = foo { ... }
Je veux vérifier si toto
est soit a
, b
ou c
, indépendamment de ce que x est.
Avec une instruction switch, je pourrais faire:
switch foo { case .a, .b, .c: ... case .d: break }
mais c'est un peu long.
J'ai pensé que je pourrais faire la même chose avec if case
:
let foo = Foo.a(x: 10)
Cela a produit une erreur de compilation.
J'ai alors trouvé cette question , et a essayé ceci:
enum Foo { case a(x: Int) case b(x: Int) case c case d }
Le compilateur pensait que le tableau était de type [Any]
, donc cela ne fonctionne pas non plus ...
Que puis-je faire sauf l'extraire comme méthode et appeler cette méthode? Y a-t-il quelque chose de nouveau dans Swift 4.2 qui résout ce problème?
3 Réponses :
Malheureusement, il n'y a pas d'autre moyen.
C'est parce que Foo.a est de type (Int) -> Foo. La raison pour laquelle vous ne pouvez pas utiliser le array.contains est parce qu'une fermeture et Foo sont des types différents donc le compilateur suppose que vous vouliez un tableau de Any.
Pour voir ce phénomène vous-même, essayez ce code:
print(type(of: Foo.a))
Vous obtiendrez (Int) -> Foo
.
Non, non. L'instruction switch
affichée par @Sweeper fonctionne très bien.
Il y a une petite goutte d'informations importantes ici, qui concernent le type de Foo.a
qui n'est pas Foo
. Mais le reste est totalement faux, impliquant notamment que Foo.a
est une fermeture.
C'est - regardez la saisie semi-automatique de Xcode lorsque vous le tapez
Si vous prévoyez de répéter ce test plusieurs fois à plusieurs endroits, alors dupliquer la version longue et longue serait en effet ennuyeux; cependant, vous pouvez simplement encapsuler ce bout de code dans une extension.
enum Foo { case a(x: Int) case b(x: Int) case c case d var isABorC: Bool { switch self { case .a, .b, .c: return true case .d: return false } } }
Alors maintenant, votre test devient quelque chose comme ceci:
if foo.isABorC { ... }
Ou vous pouvez simplement en faire une partie de la déclaration enum:
extension Foo { var isABorC: Bool { switch self { case .a, .b, .c: return true default: return false } } }
Il y a un exemple est la documentation swift (4.2) utilisant une énumération imbriquée pour implémenter les rangs d'un jeu de cartes où on pourrait ajouter une isFaceCard
var.
En fin de compte, vous n'avez pas besoin de dupliquer indéfiniment ce morceau de texte ad nauseam . Vous pouvez le masquer jusqu'à ce que vous identifiiez une solution plus élégante.
Swift ne prend pas en charge cela car les instances Foo
ont besoin d'une correspondance de modèle, car elles ne sont pas Equatable
. Et le seul séparateur qui autorise plusieurs correspondances de motifs est ,
, et cet opérateur correspond à une opération et
, vous ne pouvez pas avoir de ou
.
Une approche laide (et je dirais incorrecte ou trompeuse) serait d'ajouter la conformité à Equatable
et d'ignorer les valeurs associées:
if [Foo.a(x: 0), Foo.b(x: 0), Foo.c].contains(where: foo.sameCase) { ... } // or if foo.sameCase(.a(x: 0)) || foo.sameCase(.b(x: 0)) || foo.sameCase(.c) { ... }
Vous pouvez alors faire quelque chose comme ceci:
enum Foo { case a(x: Int) case b(x: Int) case c case d func sameCase(_ foo: Foo) -> Bool { switch self { // a little bit more verbose, but don't risk missing new cases case .a: if case .a = foo { return true } else { return false } case .b: if case .b = foo { return true } else { return false } case .c: if case .c = foo { return true } else { return false } case .d: if case .d = foo { return true } else { return false } } } }
Une autre approche consisterait à ajouter une propriété index
et à l'utiliser lors du test:
if [Foo.a(x: 0), Foo.b(x: 0), Foo.c].matchesCase(foo) { ... }
Et utilisez-le dans le sens de
extension Array where Element == Foo { func matchesCase(_ foo: Foo) -> Bool { return contains { switch ($0, foo) { case (.a, .a): return true case (.b, .b): return true case (.c, .c): return true case (.d, .d): return true default: return false } } } }
Les deux solutions sont plus verbeuses que le simple commutateur, et elles seraient faisable uniquement si vous devez les utiliser plusieurs fois.
Vous pouvez également étendre Array
avec quelque chose comme ceci:
if [Foo.a(x: 0), Foo.b(x: 0), Foo.c].map({ $0.index }).contains(foo.index) { ... }
, et utilisez-le comme ceci:
enum Foo { case a(x: Int) case b(x: Int) case c case d var index: Int { switch self { case .a: return 0 case .b: return 1 case .c: return 2 case .d: return 3 } } }
Et une quatrième solution :). Ajout d'une fonction sameCase
:
if [Foo.a(x: 0), Foo.b(x: 0), Foo.c].contains(foo) { ... }
Utilisation:
enum Foo: Equatable { case a(x: Int) case b(x: Int) case c case d static func ==(_ lhs: Foo, _ rhs: Foo) -> Bool { switch (lhs, rhs) { case (.a, .a): return true case (.b, .b): return true case (.c, .c): return true case (.d, .d): return true default: return false } } }
Le faire avec un
if
;-) ...if ({switch foo {case .a, .b, .c: return true; default: return false}} ()) { print ("match")}
Il n'y a rien de nouveau Swift 4.2 à ce sujet.
case .a, .b
est également incorrect, car ils doivent être saisis.@AnkitThakur Je peux faire
.a, .b
dans une instruction switch@Sweeper Votre problème lorsque vous utilisez l'approche contient est que Foo n'est pas égalable.
Foo.a
ce n'est même pas une syntaxe valide pour votre structureFoo.a (x: 1)
Il vous suffit donc de déclarer
enum Foo: Equatable {
et lors de l'appel deif [Foo.a (x: 1), Foo.b (x: 2), Foo.c] .contains (toto) {
@LeoDabus Je veux que le test soit vrai pour tous les
x
, pas seulement pour2
et1
.