Introduction


Inheritance is the mechanism by which one class (the Derived class) acquires the properties and behaviors of another (the Base class). This creates a hierarchy that mirrors the real world.

Deep Dive: Inheritance (Beyond the Definition)

The problem: The “copy-paste” trap

Imagine you are building a large game or a business system. You create a Rectangle class. Then you need a Square. Then a Triangle.

Without inheritance, you would find yourself copy-pasting the same code for Color, Position, and BorderWidth into every single class.

  • The Danger If you decide to change how color works, you have to find and fix it is 50 different files This is how bugs are born
  • The Solution : Inheritance allows us to define the “Shared DNA” in one place ( The Base Class) and let every other class “inherit” that DNA.

The “Genetic” Connection Think of inheritance as a Family Tree. In biology, you inherit traits from your parents (eye color, height), but you also have your own unique traits that your parents don’t have

In C#, this works exactly the same way:

  • The Base Class (The Parent): Contains the general logic that is common to everyone. It is the “Blueprint of a Blueprint.”

  • The Derived Class (The Child): It automatically “owns” everything the parent has, but it is allowed to specialize. It can add new features or even “re-define” how a parent’s trait works.

The “Is-A” Logic Gate To be a professional developer, you must use the “Is-A” Test. Inheritance creates a strict logical relationship. If you cannot say the phrase “[Child] is a [Parent]” and have it make sense, your inheritance is probably wrong.

  • Valid: A PhysicsProfessor is-a Teacher. (Inheritance works!)

  • Valid: A SaveButton is-a UIElement. (Inheritance works!)

  • Invalid: A Car is-a Engine. (No! A car has an engine, but it isn’t one. This is a different concept called Composition).

Why it matters for “The Big Picture”

Inheritance isn’t just about saving your fingers from typing. It is about Polymorphism (a fancy word for “Many Shapes”).

Because a Square is-a Polygon, the computer can treat them as the same thing when it needs to. You can tell a list of 1,000 different shapes to Draw(), and because they all inherited from the same Parent, they will all obey—even though a Square draws differently than a Circle.

“When you see class Square : Polygon, imagine that C# invisibly copies every line of code from Polygon and pastes it inside Square’s curly braces. You can’t see the code, but it is there, working and ready to use.”

Base and Derived Classes


In C#, inheritance isn’t just a concept; it’s a physical structure in your code. To understand it, you must understand the two roles a class can play.

The Base Class

A Base Class (also called a Parent Class or Superclass) is a standard class that serves as the “genetic donor.” It contains the properties and methods that are common to many different objects.

Example: The Polygon Every polygon has a number of sides. Instead of defining “sides” for every shape, we put it here:

class Polygon
{
    // We use 'public' so the Child class can see and use these variables
    public int numberOfSides;

    // Default Constructor
    public Polygon()
    {
        numberOfSides = 0;
        Console.WriteLine("1. Polygon constructor finished!");
    }

    // Constructor with parameters
    public Polygon(int sides)
    {
        numberOfSides = sides;
        Console.WriteLine("1. Polygon constructor finished!");
    }
}
The Derived Class

A Derived Class (also called a Subclass) is the class that “builds on top” of the base. It takes everything from the parent and then adds its own unique flavor.

The Syntax: The Colon (:) In C#, the colon is the “Inheritance Operator.” It tells the compiler: “Take everything inside Polygon and put it inside Square too.”

// Square inherits from Polygon
class Square : Polygon
{
    public float length;

    public Square(float s) : base(side) // alias/pointer that refers immediate parent class.
    {
        length = s;
        
        // Look! 'numberOfSides' is NOT defined in this class.
        // But we can use it because we inherited it from Polygon.
        numberOfSides = 4; 
        Console.WriteLine("2. Square constructor finished!");
    }
    
    public float GetArea() 
    {
        return length * length;
    }
    public float GetPerimeter() 
    {
        return length * numberOfSides;
    }
    
}

When you write class Square : Polygon, C# takes everything from the Polygon class and invisibly pastes it inside the Square class.

In real life, you have two parents. In C# programming, a class can only have ONE base class.

  • class Square : Polygon Perfect.
  • class Square : Polygon, Shape Error! (C# does not allow “Multiple Inheritance”).

Multilevel Inheritance While you can only have one parent, you can have a Grandparent. Inheritance can go many layers deep:

  1. Grandparent: class Shape
  2. Parent: class Polygon : Shape
  3. Child: class Square : Polygon

In this case, the Square gets everything from Polygon AND everything from Shape. It is a cumulative process.

How it looks in the Main Program When your students create a Square object, they can access variables from both classes as if they were one single unit.

Definition: Specific vs General

Specific Declaration

Square mySquare = new Square(5.5f);   // using the child is better

This uses the Specific Remote Control. It gives you access to everything: the general properties of the parent (numberOfSides) AND the unique properties of the child (length, GetArea, GetPerimeter). Use this when you need to perform square-specific math.

General Declaration

Polygon mySquare = new Square(5.5f);  // we dont' get prop and fun of square

This uses the General Remote Control. It treats the Square as a simple Polygon. You can see the numberOfSides, but you cannot see the length or call GetArea. Use this when you want to store different shapes (like Triangles and Squares) together in one list.

using System;

class Polygon
{
    public int numberOfSides;

    // Default Constructor
    public Polygon()
    {
        numberOfSides = 0;
        Console.WriteLine("1. Polygon Default Constructor finished!");
    }

    // Constructor with parameters
    public Polygon(int sides)
    {
        numberOfSides = sides;
        Console.WriteLine("1. Polygon Parameterized Constructor finished!");
    }
}

class Square : Polygon
{
    public float length;

    // We pass '4' to the parent because every Square has 4 sides
    public Square(float s) : base(4) 
    {
        length = s;
        // numberOfSides is inherited, so we can use it here too
        Console.WriteLine("2. Square Constructor finished!");
    }

    public float GetArea() 
    {
        return length * length;
    }

    public float GetPerimeter() 
    {
        // Using the inherited numberOfSides property!
        return length * numberOfSides;
    }
}

class Program
{
    static void Main()
    {
        Console.WriteLine("--- Creating a Specific Square ---");
        // Using the Child type gives us access to Area and Perimeter
        Square mySquare = new Square(5.5f);
        
        Console.WriteLine($"Length: {mySquare.length}");
        Console.WriteLine($"Sides: {mySquare.numberOfSides}");
        Console.WriteLine($"Area: {mySquare.GetArea()}");
        Console.WriteLine($"Perimeter: {mySquare.GetPerimeter()}");

        Console.WriteLine("\n--- Creating a General Polygon ---");
        // This works, but we can't call GetArea() here!
        Polygon poly = new Square(10.0f);
        Console.WriteLine($"Sides: {poly.numberOfSides}");
        
        // Console.WriteLine(poly.GetArea()); // <-- This would cause an ERROR!
    }
}
class Program
{
    static void Main()
    {
        // 1. Create an array that can hold 3 Polygons
        Polygon[] squareCollection = new Polygon[3];

        // 2. Put different sized Squares into the Polygon array
        squareCollection[0] = new Square(2.0f);
        squareCollection[1] = new Square(5.5f);
        squareCollection[2] = new Square(10.0f);

        Console.WriteLine("--- Processing the Square Collection ---");

        // 3. Loop through the array
        for (int i = 0; i < squareCollection.Length; i++)
        {
            // We can access numberOfSides because it belongs to Polygon
            Console.WriteLine($"Item {i}: This is a shape with {squareCollection[i].numberOfSides} sides.");
            
            // NOTE FOR STUDENTS: 
            // Console.WriteLine(squareCollection[i].GetArea()); 
            // The line above would fail! The 'Polygon' remote has no 'Area' button.
        }
    }
}

Deep Dive: Constructors & The Inheritance Bridge


Constructors are Not Inherited While a Square inherits every variable and method from Polygon, it does not inherit its constructors.

The Reason: A constructor’s only job is to put an object into a “Valid Starting State.”

The Problem: A Polygon constructor only knows how to set up polygon things (like numberOfSides). It has no idea that a Square exists, let alone that it needs a Size or Length property initialized.

The “Invisible” Handshake (Implicit calls) If you do not write a constructor in your Square class, or if you write one without the base keyword, C# does not just skip the parent. It automatically looks for the parent’s parameterless constructor: Polygon().

  • If Polygon() exists: Everything works silently.

  • If Polygon() DOES NOT exist: (because you only made a constructor with parameters), the code will crash/fail to compile.

The base keyword Targeted Construction To make our code cleaner, we use the base keyword. This allows the Square to “delegate” part of the work back to the Polygon.

The Syntax: The call to the base constructor happens after the parentheses but before the curly braces { }.

public Square(float size) : base(4) // This calls Polygon(4)
{
    Size = size; // This sets Square's specific data
}

full Implementation Example

class Polygon
{
    public int numberOfSides;

    // The Parent takes care of the sides
    public Polygon(int sides)
    {
        this.numberOfSides = sides;
        Console.WriteLine($"Step 1: Polygon initialized with {sides} sides.");
    }
}

class Square : Polygon
{
    public float size;

    // The Child takes care of the size, but TELLS the parent the sides
    public Square(float s) : base(4) 
    {
        this.size = s;
        Console.WriteLine($"Step 2: Square initialized with size {s}.");
    }

    public float GetPerimeter() => size * numberOfSides;
}

No Redundancy: We don’t write numberOfSides = 4 inside the Square. We let the Polygon handle its own variables.

Strict Order: C# guarantees the Polygon foundation is built before the Square decorations are added.

Safety: By using : base(4), we ensure that a Square can never accidentally be created with 3 or 5 sides.

ScenarioCode WrittenWhat C# Does
No ConstructorNothingCalls base() (the empty one) automatically.
Explicit Call: base(4)Jumps directly to Polygon(int sides).
The ResultExecutionParent code runs first $\rightarrow$ Child code runs second.

The Protected Access Modifier


The three levels of security

  • public (The Front Yard): Anyone passing by on the street (any other class) can see and touch things here.

  • private (The Safe): Only the person who owns the safe (the specific class) knows the code. Not even their children can open it.

  • protected (The Living Room): Strangers on the street can’t come in, but family members (Derived Classes) are welcome to use everything inside.

Why use protected?

If we make numberOfSides private, our Square cannot use it to calculate perimeter. If we make it public, a “Stranger” class (like a CateringCompany class) could accidentally change a Square’s sides to 100, breaking our logic.

protected is the perfect middle ground. It allows the Square to use the variable while keeping it hidden from the rest of the program.

using System;

class Polygon
{
    // PROTECTED: Square can see this, but the 'Program' class cannot.
    protected int numberOfSides;

    public Polygon(int sides)
    {
        numberOfSides = sides;
    }
}

class Square : Polygon
{
    public float size;

    public Square(float s) : base(4)
    {
        size = s;
    }

    public float GetPerimeter()
    {
        // This works because Square is a "family member" (child) of Polygon
        return size * numberOfSides; 
    }
}

class Program
{
    static void Main()
    {
        Square mySquare = new Square(5.0f);

        // SUCCESS: This works because GetPerimeter is public
        Console.WriteLine($"Perimeter: {mySquare.GetPerimeter()}");

        // ERROR: This line would fail to compile!
        // Console.WriteLine(mySquare.numberOfSides); 
        // Why? Because 'Program' is not a child of Polygon. It's a "stranger."
    }
}

class ClassA // The Parent
{
    public int X = 10;    // The "Universal" Variable
    protected int Y = 20; // The "Family" Variable
    private int Z = 30;   // The "Secret" Variable

    public void ShowZ() {
	    Console.WriteLine(X); // Only ClassA can touch X
	    Console.WriteLine(Y); // Only ClassA can touch Y
	    Console.WriteLine(Z); // Only ClassA can touch Z
	    
    } 
}

class ClassB : ClassA // The Child
{
    public void TestAccess()
    {
        Console.WriteLine(X); //  ALLOWED (Public)
        Console.WriteLine(Y); //  ALLOWED (Protected - Inherited)
        // Console.WriteLine(Z); //  ERROR (Private - Parent only)
    }
}

class Program // The Stranger (The Object User)
{
    static void Main()
    {
        ClassB obj = new ClassB();

        // --- Outside Perspective ---
        Console.WriteLine(obj.X); //  ALLOWED (Public)

        // Console.WriteLine(obj.Y); //  ERROR (Protected is hidden from strangers)
        // Console.WriteLine(obj.Z); //  ERROR (Private is hidden from strangers)
    }
}
ModifierInside the Same Class?Inside a Derived Class?Outside Classes?
publicYesYesYes
protectedYesYesNo
privateYesNoNo

The Base Class of Everything: object


The Universal Parent in C#, every class is a child of the object class. Even if you don’t write :Polygon , C# secretly write : Object for you

  • The keyword: object Which is just a nickname for System.Object
  • The Rule: a variable of type object can hold anything - a Square, a Number, a string or a custom class.

What is “Inside” the Object Class? When you create a class, you get 4 main methods for free. Think of these as the 4 Default Buttons on every “Remote Control”:

MethodWhat it does
.ToString()Converts the object to text. (Usually shows the class name unless you override it).
.Equals(obj)Checks if two objects are the same.
.GetType()Tells you exactly what the object is (e.g., “This is a Square”).
.GetHashCode()Gives a unique ID number to the object (used for sorting/searching).
The “Box” Analogy (Boxing)

When you put a specific item into an object variable, you are “Boxing” it.

  • Specific Type (Square s): You can see the size and GetArea().

  • Object Type (object o): You can only see the 4 buttons above. The size is hidden inside the box.

using System;

class Square 
{
    public float size = 5.0f;
    
    // We override the built-in ToString to make it useful
    public override string ToString() => $"Square with size {size}";
}

class Program
{
    static void Main()
    {
        // One array to hold completely different types
        object[] things = new object[3];

        things[0] = new Square(); // A Class
        things[1] = "Hello!";     // A String
        things[2] = 100;          // An Integer

        foreach (object item in things)
        {
            // We can call ToString() because EVERY object has it!
            Console.WriteLine(item.ToString());
        }
    }
}
object a = 10;                // int
object b = "Hello";           // string
object c = 4.5f;              // float
object d = new Square(5);     // Your custom class

The Sealed Keyword: Closing the inheritance Line


What does sealed do? When you mark a class as sealed, you are preventing any other class from inheriting from it. It becomes the Final Version of that class.

  • Inheritance stops here: If you try to create a class that derives from a sealed class, C# will give you a compiler error.

  • The Syntax: Put the word sealed right before the word class.

sealed class Square : Polygon
{
    // This class is now "Sealed." 
    // You can use it, but you cannot inherit from it.
}

// ERROR: This will not work!
// class ColoredSquare : Square { }
Why use sealed?

There are two main reasons to seal a class:

  1. Security & Design: You want to make sure nobody “breaks” your logic by overriding methods or adding weird behavior to your specific class. It protects your code from being misused by other developers.

  2. Performance Boost: Because the compiler knows no one will ever inherit from this class, it can make the code run slightly faster. It doesn’t have to look for “potential” child classes when calling methods.

Structs: The Naturally Sealed Types

In C#, Structs (like int, bool, or custom structs) are sealed by default.

  • You cannot inherit from a Struct.

  • A Struct cannot inherit from a Class.

  • This is why Structs are generally faster and simpler than Classes.

FeatureClass (Standard)Sealed ClassStruct
Can be Inherited?YesNoNo
Can Inherit others?YesYesNo
PerformanceStandardFasterFastest

// add note for when declare the object should we use part or child as the type?