Skip to main content

[From C# to Swift] 13. Inheritance

Learning Swift from a C# Perspective

Swift : Inheritance

Inheritance Basics and Base Class
#

1. Core Concepts
#

  • Explanation: Inheritance is the cornerstone of object-oriented programming. In Swift, a class can inherit methods, properties, and other characteristics from another class. The biggest difference between Swift and C# is that Swift classes do not inherit from a universal “root class” (like C#’s System.Object). If you define a class without specifying a superclass, it automatically becomes a “Base Class”.
  • Key Syntax: class ClassName, subclassing
  • Official Note:

Swift classes do not inherit from a universal base class. Classes you define without specifying a superclass automatically become base classes for you to build upon.

2. Example Analysis
#

Documentation Source Code:

class Vehicle {
    var currentSpeed = 0.0
    var description: String {
        return "traveling at \(currentSpeed) miles per hour"
    }
    func makeNoise() {
        // do nothing - an arbitrary vehicle doesn't necessarily make a noise
    }
}

let someVehicle = Vehicle()
print("Vehicle: \(someVehicle.description)")
// Vehicle: traveling at 0.0 miles per hour

Logic Explanation: This code defines a base class named Vehicle. It possesses a stored property currentSpeed, a read-only computed property description, and a method makeNoise. This demonstrates the basic structure of a Swift class definition.

3. C# Developer’s Perspective
#

Concept Mapping: C# Class definition.

C# Comparison Code:

public class Vehicle {
    // To allow subclasses to override, virtual properties are recommended instead of fields
    public virtual double CurrentSpeed { get; set; } = 0.0;
    
    // C# read-only property syntax, must be marked virtual to be overridden
    public virtual string Description => $"traveling at {CurrentSpeed} miles per hour";
    
    // C# requires explicit virtual marking to be overridden (if designed for inheritance modification)
    public virtual void MakeNoise() {
        // do nothing
    }
}

Key Difference Analysis:

  • Syntax: Swift’s class definition is very concise and doesn’t require public by default like C# (Swift defaults to internal).
  • Behavior: This is a key point! In C#, to make a method overridable, you must explicitly add the virtual keyword in the parent class. In Swift, class methods are overridable by default (unless marked final), so developers do not need to write virtual in the parent class.

Subclassing
#

1. Core Concepts
#

  • Explanation: Subclassing is the act of basing a new class on an existing class. The subclass inherits characteristics from the existing class, which you can then refine. You can also add new characteristics to the subclass.
  • Key Syntax: class Subclass: Superclass

2. Example Analysis
#

Documentation Source Code:

class Bicycle: Vehicle {
    var hasBasket = false
}

let bicycle = Bicycle()
bicycle.hasBasket = true
bicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")
// Bicycle: traveling at 15.0 miles per hour

class Tandem: Bicycle {
    var currentNumberOfPassengers = 0
}

let tandem = Tandem()
tandem.hasBasket = true
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22.0
print("Tandem: \(tandem.description)")
// Tandem: traveling at 22.0 miles per hour

Logic Explanation: Bicycle inherits from Vehicle and adds a hasBasket property. Tandem inherits from Bicycle, forming an inheritance chain. Subclasses can freely access and modify inherited properties (such as currentSpeed).

3. C# Developer’s Perspective
#

Concept Mapping: C# inheritance syntax is identical.

C# Comparison Code:

public class Bicycle : Vehicle {
    public bool HasBasket = false;
}
// Usage is almost identical to Swift

Key Difference Analysis:

  • Syntax: Both use the colon : to indicate inheritance; the structure is the same.
  • Behavior: The logic of the inheritance chain is completely consistent with C#.

Overriding Methods
#

1. Core Concepts
#

  • Explanation: A subclass can provide its own custom implementation of an instance method, type method, instance property, type property, or subscript that it would otherwise inherit from a superclass. Swift enforces the use of the override keyword, which helps prevent accidental overrides or spelling errors.
  • Key Syntax: override func

2. Example Analysis
#

Documentation Source Code:

class Train: Vehicle {
    override func makeNoise() {        
        print("Choo Choo")
    }
}

let train = Train()
train.makeNoise()
// Prints "Choo Choo"

Logic Explanation: The Train class overrides the makeNoise method of Vehicle. The Swift compiler checks if makeNoise actually exists in Vehicle and if the signature matches; otherwise, it reports a compilation error.

3. C# Developer’s Perspective
#

Concept Mapping: C#’s override keyword.

C# Comparison Code:

public class Train : Vehicle {
    // Must ensure the base class is marked virtual 
    public override void MakeNoise() {
        Console.WriteLine("Choo Choo");
    }
}

Key Difference Analysis:

  • Behavior:
    • C#: Opt-in model. The parent class must say “I can be overridden (virtual)” for the subclass to override.
    • Swift: Opt-out model. Parent class methods are overridable by default unless the parent class marks them as final.
    • Safety: Swift’s override is mandatory. If you accidentally write a method with the same name as the parent class but don’t add override, Swift reports an error; conversely, if you add override but the parent class lacks that method, it also reports an error. This is stricter and more intuitive than C#’s new keyword behavior.

Accessing Superclass Members
#

1. Core Concepts
#

  • Explanation: When you provide a method, property, or subscript override, it’s sometimes useful to use the existing superclass implementation as part of your override. For example, you might want to refine existing behavior or store a modified value in an inherited variable. In these cases, you access the superclass version using the super prefix.
  • Key Syntax: super
  • Usage Rules:
    • Methods: In the overriding method implementation, use super.someMethod() to call the superclass version.
    • Properties: In the overriding Getter or Setter implementation, use super.someProperty to access the superclass property value.
    • Subscripts: In the overriding subscript implementation, use super[someIndex] to access the superclass subscript logic.

2. Example Analysis
#

Syntax Demo:

class Base {
    func doSomething() { print("Base working") }
}

class Sub: Base {
    override func doSomething() {
        super.doSomething() // 1. Execute parent class's original work first
        print("Sub working") // 2. Then execute subclass's additional work
    }
}

Logic Explanation: super is typically used to “extend” rather than “completely replace” the parent class behavior.

3. C# Developer’s Perspective
#

Concept Mapping: C#’s base keyword.

C# Comparison Code:

public class Base {
    public virtual void DoSomething() { /*...*/ }
}

public class Sub : Base {
    public override void DoSomething() {
        base.DoSomething(); // C# uses base to call parent class
        // ...
    }
}

Key Difference Analysis:

  • Keyword: Swift uses super, C# uses base.

Overriding Properties
#

1. Core Concepts
#

  • Explanation: Swift allows you to override an inherited instance or type property (whether it’s a stored property or a computed property) to provide your own custom getter and setter, or to add property observers.
  • Key Syntax: override var, getter, setter
  • Official Note:

If you provide a setter as part of a property override, you must also provide a getter for that override. If you don’t want to modify the value of the inherited property within the overriding getter, you can simply pass through the inherited value by returning super.someProperty.

2. Example Analysis
#

Documentation Source Code:

class Car: Vehicle {
    var gear = 1
    override var description: String {
        return super.description + " in gear \(gear)"
    }
}

let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print("Car: \(car.description)")
// Car: traveling at 25.0 miles per hour in gear 3

Logic Explanation: Car overrides the description property of Vehicle. It retrieves the parent class’s text via super.description and appends gear information.

3. C# Developer’s Perspective
#

Concept Mapping: C# Property Overriding.

C# Comparison Code:

public class Car : Vehicle {
    public int Gear = 1;
    // Must ensure parent class Description is marked virtual
    public override string Description {
        get { return base.Description + $" in gear {Gear}"; }
    }
}

Key Difference Analysis:

  • Syntax: Swift does not distinguish between “property is a field vs. property” during inheritance; they are uniformly treated as properties when overriding.
  • Behavior:
    • Extensibility: Swift allows you to override an inherited “read-only” property as a “read-write” property (by providing both Getter and Setter). However, you cannot override a “read-write” property to be “read-only”.
    • Implementation Details: In C#, you typically cannot directly override a simple Field (variable); it must be a Property. In Swift, a parent class’s var currentSpeed = 0.0 (Stored Property) can be overridden by a subclass as a computed property or have observers added, offering immense flexibility.

Overriding Property Observers
#

1. Core Concepts
#

  • Explanation: This is a powerful feature unique to Swift. You can add property observers (willSet or didSet) to inherited properties in a subclass to be notified when the property value changes.
  • Key Syntax: didSet, willSet
  • Official Note:

You cannot add property observers to inherited constant stored properties (let) or inherited read-only computed properties, because their values cannot be set. Additionally, if you have already overridden the Setter, you cannot add observers (logic should be placed directly inside the Setter).

2. Example Analysis
#

Documentation Source Code:

class AutomaticCar: Car {
    override var currentSpeed: Double {
        didSet {
            gear = Int(currentSpeed / 10.0) + 1
        }
    }
}

let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("AutomaticCar: \(automatic.description)")
// AutomaticCar: traveling at 35.0 miles per hour in gear 4

Logic Explanation: AutomaticCar inherits from Car. When currentSpeed is modified, didSet is automatically triggered to calculate the appropriate gear. This is done without needing to modify the parent class code.

3. C# Developer’s Perspective
#

Concept Mapping: C# has no direct syntax for this; typically achieved by overriding the Setter.

C# Comparison Code:

public class AutomaticCar : Car {
    // C# must override the entire property to achieve similar effect
    public override double CurrentSpeed {
        get { return base.CurrentSpeed; }
        set {
            base.CurrentSpeed = value;
            // Simulate didSet behavior here
            Gear = (int)(value / 10.0) + 1;
        }
    }
}

Key Difference Analysis:

  • Syntax: Swift’s didSet syntax is very clean, separating “storage logic” from “observation logic”.
  • Behavior: In C#, to achieve side effects, you must rewrite the entire Setter and remember to call base.Property = value. Swift allows developers to focus solely on “what to do after change,” reducing the risk of errors (like forgetting to assign the value).

Preventing Overrides
#

1. Core Concepts
#

  • Explanation: If you want to prevent a class, method, or property from being inherited or overridden, you can use the final keyword.
  • Key Syntax: final class, final var, final func

2. Example Analysis
#

final class ImmutableVehicle {
    final func makeNoise() {
        print("No override allowed")
    }
}

Logic Explanation: After adding final, any attempt to override that member or inherit from that class will result in a compilation error. This helps with encapsulation design and can sometimes bring compiler performance optimizations (as it eliminates the need for dynamic dispatch).

3. C# Developer’s Perspective
#

Concept Mapping: C#’s sealed keyword.

C# Comparison Code:

// 1. Prevent class inheritance (corresponds to final class)
public sealed class FinalVehicle { }

// 2. Prevent method overriding (corresponds to final func)
public class Train : Vehicle {
    // In C#, methods defined by you are non-overridable by default (non-virtual).
    // But if inheriting a virtual method, use sealed override to prevent further overriding.
    public sealed override void MakeNoise() {
        Console.WriteLine("Choo Choo");
    }
}

Key Difference Analysis:

  • Syntax: Swift uses final, C# uses sealed.
  • Behavior: The meaning is identical.
    • final class = sealed class (cannot have subclasses).
    • final func = sealed override (if the method is already an override) or a non-virtual method (which is the default in C#).