Skip to main content

[From C# to Swift] 05. Control Flow

For-In Loops
#

1. Core Concepts
#

  • Concept Explanation: for-in is the primary iteration syntax in Swift. It is used to iterate over Sequences, such as Arrays, Dictionaries, Ranges, or Strings.
  • Key Syntax: for-in, ... (Closed Range), ..< (Half-Open Range), _ (Wildcard/Ignore value)
  • Note:

Dictionary iteration order is not guaranteed to be stable; do not rely on it. If a fixed order is required, please sort the keys or (key, value) pairs before processing.

2. Examples
#

let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
    print("Hello, \(name)!")
}

let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs {
    print("\(animalName)s have \(legCount) legs")
}

for index in 1...5 {
    print("\(index) times 5 is \(index * 5)")
}

Explanation:

  1. The first segment demonstrates iterating over an array; name is a constant within each iteration.
  2. The second segment demonstrates iterating over a dictionary. Swift treats each element of the dictionary as a (key, value) Tuple, allowing us to perform Decomposition directly within the loop declaration.
  3. The third segment demonstrates numeric range iteration, using the ... operator to represent a range that includes both the start and end values.

3. C#
#

Concept Correspondence: Equivalent to the C# foreach loop.

C# Examples:

var names = new[] { "Anna", "Alex", "Brian", "Jack" };
foreach (var name in names) {
    Console.WriteLine($"Hello, {name}!");
}

var numberOfLegs = new Dictionary<string, int> { {"spider", 8}, {"ant", 6}, {"cat", 4} };
foreach (var kvp in numberOfLegs) { // C# accesses via KeyValuePair
    Console.WriteLine($"{kvp.Key}s have {kvp.Value} legs");
}

foreach (var (animal, legs) in numberOfLegs) {
    Console.WriteLine($"{animal}s have {legs} legs");
}

foreach (var index in Enumerable.Range(1, 5)) {
    Console.WriteLine($"{index} times 5 is {index * 5}");
}

Key Difference Analysis:

  • Syntax: Swift’s range operators 1...5 (closed range) and 0..<5 (excludes the end) are very intuitive. C# usually requires Enumerable.Range or a traditional for loop.
  • Behavior: The iteration variable in Swift’s for-in (e.g., index) is a constant (let) by default and cannot be modified within the loop. If the variable is not needed, Swift requires using _ to explicitly ignore the unused value. This is primarily to improve readability and avoid misuse; whether it results in actual performance optimization is determined by the compiler.

While Loops
#

1. Core Concepts
#

  • Concept Explanation: Used when the number of iterations is unknown and execution must repeat until a specific condition is met. Swift provides two forms: while (check condition first) and repeat-while (check condition later).
  • Key Syntax: while, repeat-while
  • Note:

Swift’s repeat-while loop is similar to the do-while loop in other languages.

2. Examples
#

// While
while square < finalSquare {
    // Game logic...
    square += diceRoll
}

// Repeat-While
repeat {
    // Game logic...
    square += diceRoll
} while square < finalSquare

Explanation: This code demonstrates the logic for a “Snakes and Ladders” game. while evaluates the condition before executing the block; repeat-while guarantees the code inside the block executes at least once before checking the condition.

3. C#
#

Concept Correspondence: Fully corresponds to C#’s while and do-while.

C# Examples:

// C#
while (square < finalSquare) {
    // Logic
}

do {
    // Logic
} while (square < finalSquare);

Key Difference Analysis:

  • Syntax: Swift uses the keyword repeat to replace C#’s do.
  • Behavior: The behavioral logic is identical. The main difference is that Swift’s condition does not require mandatory parentheses () like C#, e.g., while square < finalSquare works fine.

Conditional Statements
#

1. Core Concepts
#

  • Concept Explanation: Swift’s if is not just a statement; it can also serve as an Expression returning a value.
  • Key Syntax: if, else if, else

2. Examples
#

// Standard usage
if temperatureInFahrenheit <= 32 {
    print("It's very cold.")
}

// If Expression (Assignment usage)
let weatherAdvice = if temperatureInCelsius <= 0 {
    "It's very cold. Consider wearing a scarf."
} else if temperatureInCelsius >= 30 {
    "It's really warm. Don't forget to wear sunscreen."
} else {
    "It's not that cold. Wear a T-shirt."
}

Logic Explanation: This demonstrates using if as an expression, assigning the result directly to the variable weatherAdvice. This makes the code more concise, eliminating the need to declare a variable before assigning it.

3. C#
#

Concept Correspondence:

  • Standard if corresponds to C# if.
  • if expression corresponds to C#’s ternary operator ? : or the newer Switch Expression.

C# Examples:

// C# uses the ternary operator to achieve a similar if expression effect
var weatherAdvice = temperatureInCelsius <= 0 ? "It's very cold..." 
    : temperatureInCelsius >= 30 ? "It's really warm..." 
    : "It's not that cold...";

Key Difference Analysis:

  • Syntax: Swift’s if condition does not require wrapping in parentheses (), but braces {} for the execution block are mandatory (even if there is only one line of code). C# allows omitting braces for single lines; Swift does not.

Switch Statements
#

1. Core Concepts
#

  • Concept Explanation: Swift’s switch is extremely powerful. It must be Exhaustive, meaning all possible cases must be handled (usually via default). Most importantly: No Implicit Fallthrough.
  • Key Syntax: switch, case, default, ... (Interval matching), (a, b) (Tuple matching), let (Value binding), where (Additional condition)
  • Note:

Although break is not required in Swift, you can use the break statement to match and ignore a specific case, or to exit early. If C-style fallthrough behavior is needed, you must explicitly use the fallthrough keyword.

2. Examples
#

let somePoint = (1, 1)
switch somePoint {
case (0, 0):
    print("origin")
case (_, 0): // Matches any x value, y is 0
    print("on the x-axis")
case (0, _): // Matches x is 0, any y value
    print("on the y-axis")
case (-2...2, -2...2): // Interval matching
    print("inside the box")
default:
    print("outside")
}

// Value Binding and Where Clauses
let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
    print("on the line x == y")
case let (x, y) where x == -y:
    print("on the line x == -y")
case let (x, y):
    print("arbitrary point (\(x), \(y))")
}

Logic Explanation:

  1. The first example demonstrates Tuple matching and the wildcard character _. Swift can match multiple values simultaneously.
  2. The second example demonstrates “Value Binding”, temporarily storing matched values as constants x, y, and applying additional logic via where clauses.

3. C#
#

Concept Correspondence: Similar to C#’s switch, especially Pattern Matching introduced in C# 8.0+.

C# Examples:

// C# 8.0+ Pattern Matching
var somePoint = (1, 1);
switch (somePoint) {
    case (0, 0):
        Console.WriteLine("origin");
        break;
    case (var x, 0): // Similar to Swift's (_, 0) but syntax differs slightly
        Console.WriteLine($"on x-axis at {x}");
        break;
    case (0, _):
        Console.WriteLine("on y-axis");
        break;
    case (var x, var y) when x == y: // Corresponds to Swift's case let ... where
        Console.WriteLine("x == y");
        break;
    default:
        Console.WriteLine("outside");
        break;
}

Key Difference Analysis:

  • Syntax: Swift does not require writing break, which is the biggest habit change. In C#, empty case labels can fall through directly (stacked labels), but cases with code cannot implicitly fall through (requiring break or goto case); Swift completely forbids implicit fallthrough, requiring the fallthrough keyword.
  • Behavior: Swift’s switch must handle all possibilities. This is particularly useful for Enums; when a member is added to an Enum, the compiler forces you to update the switch code, reducing bugs.
  • Compound Cases: Swift supports syntax like case "a", "b":; C# traditionally uses stacked case labels, though modern pattern matching also supports or logic.

Control Transfer Statements
#

1. Core Concepts
#

  • Concept Explanation: Statements that change the order of code execution. Includes continue (skip this loop iteration), break (exit loop or switch), and fallthrough (switch fallthrough).
  • Key Syntax: continue, break, fallthrough

2. Examples
#

let integerToDescribe = 5
var description = "The number \(integerToDescribe) is"
switch integerToDescribe {
case 2, 3, 5, 7, 11, 13, 17, 19:
    description += " a prime number, and also"
    fallthrough // Explicitly request fallthrough
default:
    description += " an integer."
}
// Result: "The number 5 is a prime number, and also an integer."

Logic Explanation: Since Swift switch statements default to finishing after matching a case, if you wish to execute the code in case 2 (or default) after case 1 finishes, you must add fallthrough.

3. C#
#

Concept Correspondence:

  • continue / break match C# behavior.
  • fallthrough corresponds to C#’s goto case or goto default.

C# Examples:

switch (integerToDescribe) {
    case 2:
    case 3:
    // ...
    case 19:
        description += " a prime number, and also";
        goto default; // C# explicit fallthrough
    default:
        description += " an integer.";
        break;
}

Key Difference Analysis:

  • Syntax: Swift’s fallthrough does not require specifying a target; it simply “falls into” the execution block of the next case, without checking the next case’s condition. This is very important and can be a source of bugs, so use it with caution.
  • Similarities: continue and break behave exactly the same in Swift and C#.

Labeled Statements
#

1. Core Concepts
#

  • Concept Explanation: When dealing with multiple nested loops, you can use Labels to specify which loop a break or continue statement applies to.
  • Key Syntax: labelName: while ...

2. Examples
#

gameLoop: while square != finalSquare {
    diceRoll += 1
    switch square + diceRoll {
    case finalSquare:
        break gameLoop // Break out of the outer while loop directly, not just the switch
    case let newSquare where newSquare > finalSquare:
        continue gameLoop // Dice roll exceeds finish, skip this turn, roll again
    default:
        square += diceRoll
    }
}

Logic Explanation:

  • If no label is used, break only exits the switch statement. By adding the gameLoop label, break gameLoop directly terminates the entire while loop.
  • Similarly, continue gameLoop skips the remainder of the current iteration of the while loop and jumps directly to the while condition check for the next iteration.

3. C#
#

Concept Correspondence: C# has goto labels, but does not have this specific break label syntax attached directly to loop structures.

C# Examples:

// Common C# approach
bool keepPlaying = true;
while (keepPlaying) {
    // ...
    switch (condition) {
        case 1:
            keepPlaying = false; // Use a flag
            break; 
        // Or use goto
        case 2:
            goto EndOfLoop;
    }
}
EndOfLoop: ;

Key Difference Analysis:

  • Syntax: Swift’s label syntax is more structured and readable than C#’s goto, specifically designed for nested loop control.

Early Exit (Guard)
#

1. Core Concepts
#

  • Concept Explanation: guard is a “reverse if”. It requires the condition to be true, otherwise it executes the else block. The else block must contain an instruction to exit the current scope (e.g., return, break, throw). This avoids “arrow code” (excessive nested ifs) and keeps the “happy path” at the top level.
  • Key Syntax: guard condition else { return }

2. Examples
#

func greet(person: [String: String]) {
    guard let name = person["name"] else {
        return // Condition not met, must exit
    }
    // name can be used directly here, not needed inside an else block
    print("Hello \(name)!")
    
    guard let location = person["location"] else {
        print("I hope the weather is nice near you.")
        return
    }
    print("I hope the weather is nice in \(location).")
}

Logic Explanation:

  1. Check if person["name"] has a value.
  2. If it has no value, enter else and return.
  3. If it has a value, the unwrapped name variable can be used in the code following the guard statement (this is the biggest difference from if let).

3. C#
#

Concept Correspondence: C# does not have a guard keyword. The “Guard Clauses” pattern (reverse if) is typically used.

C# Examples:

void Greet(Dictionary<string, string> person) {
    if (!person.TryGetValue("name", out var name)) {
        return;
    }
    // C# out variable leaks to outer scope, similar to guard effect
    Console.WriteLine($"Hello {name}!");
}

Key Difference Analysis:

  • Syntax: Swift’s guard enforces that the else block must exit, guaranteeing safety at compile time. C# relies on developer coding habits.
  • Scope: The variable bound by guard let is scoped to the entire block containing the guard statement (until the end), making the code flow very smooth.

Deferred Execution (Defer)
#

1. Core Concepts
#

  • Concept Explanation: Code in a defer block executes “just before the current scope ends”, regardless of whether it finishes successfully, returns, or throws an error. Typically used for resource cleanup (e.g., closing files, releasing locks).
  • Key Syntax: defer { ... }

2. Examples
#

var score = 1
if score < 10 {
    defer {
        print(score)
    }
    score += 5
}
// Prints "6"

Logic Explanation:

  1. Enter if.
  2. Declare defer; the print statement is pushed onto the stack to wait for scope exit.
  3. Execute score += 5; score becomes 6.
  4. Prepare to exit if scope; execute defer block, printing 6.

3. C#
#

Concept Correspondence: Similar to C#’s try-finally block or using statement (IDisposable).

C# Examples:

// C# uses try-finally to simulate
{
    try {
        score += 5;
    } finally {
        Console.WriteLine(score);
    }
}

// Or use using (if resource disposal is involved)
using (var resource = new Resource()) {
    // code
} // Dispose called here

Key Difference Analysis:

  • Flexibility: defer does not require objects to implement interfaces, nor does it need nested layers of try-finally. You can insert a defer anywhere in the middle of your code to handle cleanup, and multiple defer blocks are executed in LIFO (Last In, First Out) order.