Skip to main content

[From C# to Swift] 12. Subscripts

Subscript Syntax
#

1. Core Concepts
#

  • Concept Explanation: Swift Subscripts allow for quick access to member elements within a class, struct, or enum using square brackets []. This is the mechanism used when accessing an Array via someArray[index] or a Dictionary via someDictionary[key].
  • Key Syntax: subscript, get, set

2. Example
#

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 for integers.

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

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

3. C#
#

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

C# Example:

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 Difference Analysis:

  • Syntax Aspect:
    • Definition Style: C# uses the this keyword followed by square brackets public int this[int index] to define it; Swift uses the dedicated keyword subscript, and the syntax is more like a function definition subscript(index: Int) -> Int.

Subscript Usage and Dictionary
#

1. Core Concepts
#

  • Concept Explanation: The specific meaning of a subscript depends on the context. The most common use is as a shortcut for a Collection. You are free to implement subscripts according to business logic. Swift’s Dictionary uses subscripts to set and retrieve Key-Value pairs.

2. Example
#

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. Via 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. Swift’s Dictionary subscript accepts an Optional when setting, allowing you to express “removing an element” by assigning nil.

3. C#
#

Concept Mapping: This corresponds to the indexer operation of C# Dictionary<TKey, TValue>.

C# Example:

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

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

// But behavior differs when reading:
// int legs = numberOfLegs["dragon"]; // C# will throw KeyNotFoundException

Key Difference Analysis:

  • Behavioral Aspect:
    • Swift: dictionary[key] returns an Optional (e.g., Int?). If the Key doesn’t exist, it returns nil and does not crash. This forces developers to handle cases where the value might be missing.
    • C#: dictionary[key] throws a KeyNotFoundException immediately upon reading if the Key does not exist. C# developers usually need to defend using ContainsKey or TryGetValue.
  • Removing Elements: Swift can remove an element via dict[key] = nil; C# must call dict.Remove(key). Setting the value to null (if it’s a Reference type) does not remove the Key.

Subscript Options and Multi-Parameters
#

1. Core Concepts
#

  • Concept Explanation: Subscripts can take any number of input parameters, and these parameters can be of any type. Subscripts can also be overloaded, meaning you can define multiple subscripts with different signatures. This is very useful when handling multi-dimensional data structures (like matrices).
  • Note: Subscripts can use Variadic Parameters and default parameter values, but cannot use in-out parameters.

2. Example
#

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 2D matrix flattened into a 1D array grid.
  2. It defines a subscript accepting two parameters subscript(row: Int, column: Int).
  3. assert is used in both get and set to ensure indices do not go out of bounds.
  4. Usage involves separating parameters with commas: matrix[0, 1].

3. C#
#

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

C# Example:

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 Difference Analysis:

  • Syntax Aspect: On the call site, Swift’s matrix[0, 1] is consistent with C#’s matrix[0, 1] syntax.
  • Parameter Restrictions: Swift explicitly prohibits in-out parameters in subscripts, which is similar to the restriction where C# indexers cannot use ref or out parameters.

Type Subscripts
#

1. Core Concepts
#

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

2. Example
#

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. In the Planet enum, a type subscript is defined using static subscript.
  2. When calling, you don’t 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#
#

Concept Mapping: C# currently does not support static indexers.