Skip to main content

[From C# to Swift] 08. Enumerations

Learning Swift from a C# Perspective

Swift : Enumerations

Basic Syntax (Enumeration Syntax)
#

1. Core Concepts
#

  • Concept Explanation: In Swift, an Enumeration defines a common type for a group of related values. Unlike C or C#, Swift Enums are not just aliases for integers. They are First-class types that possess their own properties, methods, and can even implement Protocols. By default, Swift Cases are not automatically assigned integer values (like 0, 1, 2).
  • Key Syntax: enum, case
  • Official Note:

This is unlike C and Objective-C. In Swift, enumeration cases are first-class values in their own right. They adopt many features traditionally supported only by classes.

2. Example Analysis
#

Documentation Source Code:

enum CompassPoint {
    case north
    case south
    case east
    case west
}

var directionToHead = CompassPoint.west
directionToHead = .east // Type inferred, type name can be omitted

Logic Explanation: Here, an enumeration named CompassPoint is defined. We define four cardinal directions. Note that once the type of the variable directionToHead is inferred as CompassPoint, subsequent assignments can use the shorthand syntax .east. This keeps the code very concise.

3. C# Developer Perspective
#

Concept Correspondence: This is equivalent to C#’s enum, but the underlying implementation is completely different.

C# Comparison Code:

enum CompassPoint {
    North,
    South,
    East,
    West
}
// Must write full name in C# (unless using static is used)
var direction = CompassPoint.West;

Key Difference Analysis:

  • Syntax: C# defaults North to 0. Swift’s north is just north; it does not equal 0.
  • Behavior: Swift’s dot syntax (.east) relies on Type Inference, which is more concise than C#. In C#, Enums are essentially wrappers around numeric Value Types; in Swift, Enums are a more abstract Algebraic Data Type.

Matching Enumeration Values with a Switch Statement
#

1. Core Concepts
#

  • Concept Explanation: Swift uses switch to match enumeration values. The most important point is Exhaustiveness, meaning you must handle every possible case, otherwise the compiler will report an error. This forces developers to check all relevant logic when adding new enumeration members.
  • Key Syntax: switch, case, default

2. Example Analysis
#

Documentation Source Code:

directionToHead = .south
switch directionToHead {
case .north:
    print("Lots of planets have a north")
case .south:
    print("Watch out for penguins")
case .east:
    print("Where the sun rises")
case .west:
    print("Where the skies are blue")
}

Logic Explanation: This code checks the value of directionToHead. Since we have listed all four directions, the compiler confirms that all cases are covered, so no default branch is needed.

3. C# Developer Perspective
#

Concept Correspondence: Corresponds to the C# switch statement or C# 8.0+ switch expressions.

C# Comparison Code:

switch (direction)
{
    case CompassPoint.North:
        Console.WriteLine("...");
        break;
    case CompassPoint.South:
        Console.WriteLine("Watch out for penguins");
        break;
    // The C# compiler usually doesn't force you to list all enum cases,
    // although modern IDEs will give a warning, it is not a compile error.
}

Key Difference Analysis:

  • Behavior: Swift’s mandatory Exhaustiveness is an excellent safety feature. In C#, if you forget to handle a new Enum member, the program might silently execute past it; in Swift, the code will fail to compile, forcing you to fix it.

Iterating over Enumeration Cases
#

1. Core Concepts
#

  • Concept Explanation: Sometimes we need to get a list of all possible values in an enumeration (e.g., to create a menu). Swift provides a standard way to do this: make the Enum conform to the CaseIterable protocol.
  • Key Syntax: CaseIterable, .allCases

2. Example Analysis
#

Documentation Source Code:

enum Beverage: CaseIterable {
    case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")

for beverage in Beverage.allCases {
    print(beverage)
}

Logic Explanation: After adding CaseIterable, the compiler automatically generates an allCases collection property. You can directly loop through it or count the number of items.

3. C# Developer Perspective
#

Concept Correspondence: C# has no built-in interface to do this and usually relies on Reflection.

C# Comparison Code:

enum Beverage { Coffee, Tea, Juice }

// Modern C# (.NET 5+) uses generic methods
foreach (Beverage b in Enum.GetValues<Beverage>())
{
    Console.WriteLine(b);
}

Key Difference Analysis:

  • Syntax: Swift’s CaseIterable is generated at compile-time, making it more type-safe and performant. C#’s Enum.GetValues relies on runtime reflection, which has higher overhead and more verbose syntax.

Associated Values
#

1. Core Concepts
#

  • Concept Explanation: This is the biggest dividing line between Swift Enums and C# Enums. In Swift, each case of an Enum can carry custom data of different types. This is called “Associated Values”. This turns Enums into Discriminated Unions (or Tagged Unions).
  • Key Syntax: case name(Type)
  • Official Note:

This behavior is similar to discriminated unions, tagged unions, or variants in other languages.

2. Example Analysis
#

Documentation Source Code:

enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}

var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")

switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
    print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
    print("QR code: \(productCode).")
}

Logic Explanation: Barcode can be a upc composed of four integers, OR a qrCode composed of a string. It cannot be both at the same time. In the switch statement, we can use let to destructure this internal data for use.

3. C# Developer Perspective
#

Concept Correspondence: Standard C# enum is completely incapable of doing this. To simulate this pattern in C#, you usually need to use abstract classes with inheritance, or use third-party libraries like OneOf.

C# Comparison Code (Simulation):

// Modern C# can use records to simplify Discriminated Unions simulation
abstract record Barcode;
record Upc(int N1, int N2, int N3, int N4) : Barcode;
record QrCode(string Code) : Barcode;

// Type checking required when using (Pattern Matching)
Barcode bar = new QrCode("ABC");
switch (bar)
{
    case Upc u:
        Console.WriteLine($"UPC: {u.N1}...");
        break;
    case QrCode q:
        Console.WriteLine($"QR: {q.Code}");
        break;
}

Key Difference Analysis:

  • Behavior: Swift Enums are Value Types and are very lightweight. The C# example above uses Class/Record (Reference Types) for simulation, which is a heavier structure.
  • Mindset Shift: C# developers are used to viewing Enums as “State Flags”; Swift developers view Enums as “State Containers carrying data”.

Raw Values
#

1. Core Concepts
#

  • Concept Explanation: If you miss C#-style Enums (where each case corresponds to a fixed value), Swift supports this via Raw Values. These values are fixed at definition time and must be of the same type (e.g., all Int or all String).
  • Key Syntax: enum Name: Type, .rawValue

2. Example Analysis
#

Documentation Source Code:

enum ASCIIControlCharacter: Character {
    case tab = "\t"
    case lineFeed = "\n"
    case carriageReturn = "\r"
}

enum CompassPoint: String {
    case north, south, east, west
}
// CompassPoint.south.rawValue is "south" (Implicit assignment)

Logic Explanation: By adding : Type after the Enum name, we define the type of the raw values. Swift even supports String as a raw value, and if not specified, it will directly use the case name as the string value, which is very convenient.

3. C# Developer Perspective
#

Concept Correspondence: This is standard C# enum behavior, but supports more types.

C# Comparison Code:

enum Planet : int { // C# can only be integer types (byte, int, long...)
    Mercury = 1,
    Venus,
    Earth
}

Key Difference Analysis:

  • Syntax: C# Enums are restricted to integers at the lowest level. Swift Raw Values can be String, Character, Int, Float, etc.
  • Behavior: In Swift, accessing the underlying value requires explicitly calling the .rawValue property; you cannot directly cast it like in C#.

Initializing from a Raw Value
#

1. Core Concepts
#

  • Concept Explanation: When an Enum has raw values, you can create an Enum instance backwards using that raw value. However, since the passed value might be invalid (e.g., passing 999 when no corresponding case is defined), this initializer is Failable, returning an Optional (Enum?).
  • Key Syntax: init(rawValue:)
  • Official Note:

The raw value initializer is a failable initializer, because not every raw value will return an enumeration case.

2. Example Analysis
#

Documentation Source Code:

let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet type is Planet?

let positionToFind = 11
if let somePlanet = Planet(rawValue: positionToFind) {
    switch somePlanet {
    case .earth:
        print("Mostly harmless")
    default:
        print("Not a safe place for humans")
    }
} else {
    print("There isn't a planet at position \(positionToFind)")
}

Logic Explanation: Using Planet(rawValue: 11) attempts to create an instance. Since 11 is out of the defined range, it returns nil. Here, Swift’s if let (Optional Binding) is used to safely handle the result.

3. C# Developer Perspective
#

Concept Correspondence: C# explicit casting (Enum)intValue.

C# Comparison Code:

int position = 11;
Planet p = (Planet)position; // C# allows this! p's value is now 11, even if undefined

if (Enum.IsDefined(typeof(Planet), p)) {
    // Safety check
} else {
    Console.WriteLine("Invalid planet");
}

Key Difference Analysis:

  • Behavior: This is a common pitfall for C# developers. C# allows you to cast any integer to an Enum, even if the value is not defined in the Enum, which can lead to runtime logic errors. Swift’s init(rawValue:) mandates returning an Optional, forcing the developer to handle “conversion failure” scenarios, resulting in much higher safety.

Recursive Enumerations
#

1. Core Concepts
#

  • Concept Explanation: When an Enum’s Associated Value contains the Enum itself, this is a recursive enumeration. This is very useful for describing tree structures (like mathematical expressions). Since Enums store their associated values inline by default, a recursive definition prevents the compiler from determining the type size at compile time. Therefore, indirect is used to tell the compiler to use indirect storage (heap allocation) instead.
  • Key Syntax: indirect case, indirect enum

2. Example Analysis
#

Documentation Source Code:

indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}

let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))

func evaluate(_ expression: ArithmeticExpression) -> Int {
    switch expression {
    case let .number(value):
        return value
    case let .addition(left, right):
        return evaluate(left) + evaluate(right)
    case let .multiplication(left, right):
        return evaluate(left) * evaluate(right)
    }
}
print(evaluate(product)) // Prints "18"

Logic Explanation: Here, a mathematical expression structure is defined. (5 + 4) * 2 is elegantly represented using the Enum structure. The evaluate function calculates the result by recursively calling itself.

3. C# Developer Perspective
#

Concept Correspondence: C# Enums do not support this feature. In C#, this is typically implemented using a recursive Class structure (i.e., the Composite Pattern).

C# Comparison Code:

abstract class Expression { }
class Number : Expression { public int Value; }
class Addition : Expression { public Expression Left; public Expression Right; }
// ...

Key Difference Analysis:

  • Syntax: Swift allows the use of indirect within a Value Type (Enum) to implement recursive structures, which is very concise syntactically and follows a functional programming style. C# requires using Reference Types (Classes) to build this kind of data structure.