Skip to main content

[From C# to Swift] 13. Inheritance

Inheritance Basics and Base Class
#

1. Core Concepts
#

  • Concept Explanation: Inheritance is the cornerstone of object-oriented programming. In Swift, a class can inherit methods, properties, and other characteristics from another class.
  • Note:

Swift classes do not inherit from a universal base class. If you do not specify a superclass when defining a class, that class automatically becomes a base class.

2. Example
#

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 has 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#
#

Concept Mapping: C# Class Definition.

C# Example:

public class Vehicle {
    // To allow subclasses to override, it is recommended to use virtual properties 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 Differences Analysis:

  • Behavioral: In C#, to make a method overridable, you must explicitly add the virtual keyword in the parent class. However, in Swift, class methods can be overridden by subclasses by default (unless marked final), so developers do not need to write virtual in the parent class.

Subclassing
#

1. Core Concepts
#

  • Concept Explanation: Subclassing is the act of creating a new class based on an existing class. The subclass inherits all characteristics of the parent class and can add new properties or methods.
  • Key Syntax: class Subclass: Superclass

2. Example
#

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 the hasBasket property. Tandem inherits from Bicycle, forming an inheritance chain. Subclasses can freely access and modify inherited properties (such as currentSpeed).

3. C#
#

Concept Mapping: C# inheritance syntax is consistent.


Overriding Methods
#

1. Core Concepts
#

  • Concept Explanation: Subclasses can provide their own implementation to replace the parent class’s behavior. Swift uses the override keyword, which helps prevent accidental overrides or naming errors.
  • Key Syntax: override func

2. Example
#

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

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

Logic Explanation: The Train class overrides Vehicle’s makeNoise method. The Swift compiler checks if makeNoise actually exists in Vehicle; if it doesn’t exist or the signature doesn’t match, the compilation will report an error.

3. C#
#

Concept Mapping: C#’s override keyword.

C# Example:

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

Key Differences Analysis:

  • Behavioral:
    • C#: Parent class must be virtual, subclass can then override.
    • Swift: Parent class methods are overridable by default, unless the parent class is marked final.

Accessing Superclass Members
#

1. Core Concepts
#

  • Concept Explanation: When overriding a method, property, or subscript in a subclass, if you need to use the parent class’s existing implementation, access the parent class version via the super prefix.
  • Key Syntax: super
  • Usage Rules:
    • Methods: In the overridden method implementation, use super.someMethod() to call the parent class version.
    • Properties: In the overridden Getter or Setter implementation, use super.someProperty to access the parent class property value.
    • Subscripts: In the overridden subscript implementation, use super[someIndex] to access the parent class subscript logic.

2. Example
#

Syntax Illustration:

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

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

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

3. C#
#

Concept Mapping: C#’s base keyword.

C# Example:

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

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

Key Differences Analysis:

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

Overriding Properties
#

1. Core Concepts
#

  • Concept Explanation: Swift allows you to override inherited properties to provide custom Getters/Setters or add property observers.
  • Key Syntax: override var, getter, setter
  • Note:

If you provide a Setter when overriding a property, you must also provide a Getter. If you don’t want to modify the Getter logic, you can simply return super.someProperty.

2. Example
#

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#
#

Concept Mapping: C# property overriding.

C# Example:

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

  • Behavioral:
    • Extensibility: Swift allows overriding an inherited “read-only” property into a “read-write” property (providing both Getter and Setter). However, you cannot override a “read-write” property into a “read-only” one.
    • Implementation Details: In C#, you generally cannot directly override a simple Field (variable); it must be a Property.

Overriding Property Observers
#

1. Core Concepts
#

  • Concept Explanation: You can add willSet or didSet observers to inherited properties in a subclass to be notified when the property value changes.
  • Key Syntax: didSet, willSet
  • Note:

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

2. Example
#

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 does not require modifying the parent class code at all.

3. C#
#

Concept Mapping: C# has no direct equivalent syntax; implemented by overriding the Setter.

C# Example:

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

Key Differences Analysis:

  • Behavioral: Swift’s didSet syntax is very clean, separating “storage logic” from “observation logic”. In C#, to achieve side effects, you must rewrite the entire Setter and remember to call base.Property = value.

Preventing Overrides
#

1. Core Concepts
#

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

2. Example
#

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.

3. C#
#

Concept Mapping: C#’s sealed keyword.

C# Example:

// 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 you define yourself are not overridable by default (non-virtual).
    // However, if inheriting a virtual method from a parent class, use sealed override to prevent subsequent subclasses from overriding it.
    public sealed override void MakeNoise() {
        Console.WriteLine("Choo Choo");
    }
}

Key Differences Analysis:

  • Syntax: Swift uses final, C# uses sealed.