Skip to main content

[From C# to Swift] 12. Subscripts

Learning Swift from a C# Perspective

Swift : Subscripts

Subscript Syntax
#

1. Core Concepts
#

  • Concept Explanation: Swift Subscripts allow you to quickly access member elements within a Class, Structure, or Enumeration using brackets []. This is the mechanism behind accessing an Array via someArray[index] or a Dictionary via someDictionary[key]. You do not need to explicitly define methods like getX / setX; instead, a subscript unifies the description of access behavior.
  • Key Syntax: subscript, get, set, newValue

2. Example Analysis
#

Documentation Source Code:

struct TimesTable {
    let multiplier: Int
    subscript(index: Int) -> Int {
        return multiplier * index
    }
}
let threeTimesTable = TimesTable(multiplier: 3)
print("six times three is \(threeTimesTable[6])")
// Prints "six times three is 18".

Logic Explanation: This code defines a structure named TimesTable to calculate a multiplication table.

  1. The subscript keyword is used to define subscript behavior.
  2. The get keyword is omitted here, indicating this is a Read-Only subscript.
  3. When we call threeTimesTable[6], Swift executes the logic inside the subscript block and returns the result of 3 * 6.

Note A multiplication table is based on fixed mathematical rules, so setting threeTimesTable[someIndex] to a new value does not make sense. Therefore, the TimesTable subscript is defined as read-only.

3. C# Developer’s Perspective
#

Concept Mapping: Swift’s subscript corresponds directly to C# Indexers (this[...]).

C# Comparison Code:

public struct TimesTable
{
    private readonly int _multiplier;

    public TimesTable(int multiplier)
    {
        _multiplier = multiplier;
    }

    // C# Indexer
    public int this[int index]
    {
        get { return _multiplier * index; }
        // Read-only, so set is not implemented
    }
}

var threeTimesTable = new TimesTable(3);
Console.WriteLine($"six times three is {threeTimesTable[6]}");

Key Differences Analysis:

  • Syntax:
    • Definition: C# uses the this keyword with brackets public int this[int index]; Swift uses the specific keyword subscript, and the syntax resembles a function definition: subscript(index: Int) -> Int.
    • Parameter Names: C# indexer parameter names are used internally; Swift parameter names are used for internal logic but can also have external Argument Labels (like function parameters), although labels are usually omitted in calls by default.
  • Behavior:
    • The logic for defining get and set is very similar. Swift’s set has a default newValue parameter, which is completely consistent with the value keyword in C#.

Subscript Usage and Dictionary
#

1. Core Concepts
#

  • Concept Explanation: The specific meaning of a subscript depends on the context. Its most common use is as a shortcut for Collections. You can implement subscripts freely based on business logic. Swift’s Dictionary uses subscripts to set and retrieve Key-Value pairs.
  • Key Syntax: Dictionary, Optional, nil

2. Example Analysis
#

Documentation Source Code:

var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
numberOfLegs["bird"] = 2

Logic Explanation: This code demonstrates subscript usage with a dictionary.

  1. numberOfLegs is a dictionary of type [String: Int].
  2. Through numberOfLegs["bird"] = 2, we use subscript syntax to add a new key-value pair.

Note The subscript implemented by Swift’s Dictionary type returns an Optional type (e.g., Int?). This is because the Key you are querying might not exist in the dictionary. The Swift Dictionary subscript accepts an Optional when setting, allowing you to assign nil to express the semantics of “removing an element.”

3. C# Developer’s Perspective
#

Concept Mapping: This corresponds to the indexer operation of C# Dictionary<TKey, TValue>, but the behavior when handling “Key does not exist” differs significantly.

C# Comparison Code:

var numberOfLegs = new Dictionary<string, int>
{
    { "spider", 8 },
    { "ant", 6 },
    { "cat", 4 }
};

numberOfLegs["bird"] = 2; // Set or add value, syntax is the same

// However, behavior differs when reading:
// int legs = numberOfLegs["dragon"]; // C# throws KeyNotFoundException

Key Differences Analysis:

  • Behavior (Crucial):
    • Swift: dictionary[key] returns an Optional (e.g., Int?). If the Key does not exist, it returns nil and does not crash. This forces developers to handle the possibility of the value being empty.
    • C#: dictionary[key] throws a KeyNotFoundException immediately if the Key does not exist during a read operation. C# developers usually need to defend against this using ContainsKey or TryGetValue.
  • Removing Elements: Swift can remove an element via dict[key] = nil; C# must call dict.Remove(key). Setting a value to null (if it is a Reference type) does not remove the Key itself.

Subscript Options and Multiple Parameters
#

1. Core Concepts
#

  • Concept Explanation: Subscripts can accept any number of parameters, and parameters can be of any type. This is known as subscript overloading. This is particularly useful when dealing with multi-dimensional data structures (like matrices).
  • Key Syntax: assert, Multiple Parameters
  • Official Hint: Subscripts can use Variadic Parameters and default parameter values, but cannot use in-out parameters.

2. Example Analysis
#

Documentation Source Code:

struct Matrix {
    let rows: Int, columns: Int
    var grid: [Double]
    init(rows: Int, columns: Int) {
        self.rows = rows
        self.columns = columns
        grid = Array(repeating: 0.0, count: rows * columns)
    }
    func indexIsValid(row: Int, column: Int) -> Bool {
        return row >= 0 && row < rows && column >= 0 && column < columns
    }
    subscript(row: Int, column: Int) -> Double {
        get {
            assert(indexIsValid(row: row, column: column), "Index out of range")
            return grid[(row * columns) + column]
        }
        set {
            assert(indexIsValid(row: row, column: column), "Index out of range")
            grid[(row * columns) + column] = newValue
        }
    }
}

var matrix = Matrix(rows: 2, columns: 2)
matrix[0, 1] = 1.5
matrix[1, 0] = 3.2

Logic Explanation:

  1. The Matrix structure stores a flattened 2D matrix in a 1D array grid.
  2. A subscript accepting two parameters is defined: subscript(row: Int, column: Int).
  3. assert is used in both get and set to ensure the index is not out of bounds.
  4. When used, parameters are separated by commas: matrix[0, 1].

3. C# Developer’s Perspective
#

Concept Mapping: C# Indexers also support multiple parameters, often used for multi-dimensional arrays or matrix-like structures.

C# Comparison Code:

public struct Matrix
{
    private readonly int _rows;
    private readonly int _columns;
    private double[] _grid;

    public Matrix(int rows, int columns)
    {
        _rows = rows;
        _columns = columns;
        _grid = new double[rows * columns];
    }

    public double this[int row, int col]
    {
        get
        {
            // Simplified boundary check
            return _grid[(row * _columns) + col];
        }
        set
        {
            _grid[(row * _columns) + col] = value;
        }
    }
}

var matrix = new Matrix(2, 2);
matrix[0, 1] = 1.5; // C# syntax is exactly the same

Key Differences Analysis:

  • Syntax: At the call site, Swift’s matrix[0, 1] and C#’s matrix[0, 1] are syntactically identical.
  • Parameter Restrictions: Swift explicitly forbids in-out parameters in subscripts, which is similar to the restriction in C# where indexers cannot use ref or out parameters.

Type Subscripts
#

1. Core Concepts
#

  • Concept Explanation: The examples above are all “Instance Subscripts,” meaning you must create an object instance to use them. Swift further supports Type Subscripts, which belong to the “type itself” and are called directly on the type name.
  • Key Syntax: static subscript, class subscript (for class inheritance)

2. Example Analysis
#

Documentation Source Code:

enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
    static subscript(n: Int) -> Planet {
        return Planet(rawValue: n)!
    }
}
let mars = Planet[4]
print(mars)

Logic Explanation:

  1. Inside the Planet enumeration, a type subscript is defined using static subscript.
  2. When calling, you do not need to instantiate Planet; you use the type name directly: Planet[4].
  3. Here, it uses rawValue to initialize and force unwrap (!) to return the corresponding planet.

3. C# Developer’s Perspective
#

Concept Mapping: This is a feature that C# currently does not possess.

Key Differences Analysis:

  • Syntax: C# does not support syntax like static this[...]. You cannot write ClassName[index] in C#.
  • Alternatives: In C#, to achieve a similar effect, one typically uses Static Methods or static read-only collections.
    // C# Alternative Approach
    public enum Planet { Mercury = 1, Venus, Earth, Mars, ... }
    
    public static class PlanetExtensions {
        public static Planet Get(int n) {
            return (Planet)n;
        }
    }
    
    // Call
    var mars = PlanetExtensions.Get(4);
  • Behavior: Swift’s Type Subscripts make the syntax more consistent, extending the concept of “access via index” to the type level. This is a feature that C# developers will find relatively novel and convenient when transitioning.