Understanding C# Inheritance, Polymorphism, and Object-Oriented Principles

Inheritance Fundamentals

Inheritance establishes a parent-child relationship between classes where child classes automatically acquire members from their parent. This relationship follows a strict hierarchy—once established, it cannot be reversed. Properties and characteristics flow downward through the inheritance chain, meaning each descendant inherits everything from its ancestors.

The primary advantages include code reusability and maintainability. When modifications are needed, updating the base class propagates changes throughout all derived classes automatically.

Implementation involves using a colon after the derived class name, followed by the base class. For instance, class Car : Vehicle establishes that Car inherits from Vehicle. In this terminology, the derived class is often called a subclass or child class, while the base class may be referred to as a parent class or superclass.

The inheritance relationship must satisfy an "is-a" condition. For example, a Car is a Vehicle, so inheritance makes sense. However, composition (has-a relationship) would be more appropriate for scenarios where a class contains another rather than being a specialized version of it.

Constructor Execution in Inheritance

When deriving classes are instantiated, the base class constructor runs first. The base keyword enables explicit calls to parent constructors, while this allows referencing members within the current class context.

public class Vehicle
{
    public string Brand { get; set; }
    public string Model { get; set; }
    public int Year { get; set; }
    
    public Vehicle() { }
    
    public Vehicle(string brand, string model, int year)
    {
        this.Brand = brand;
        this.Model = model;
        this.Year = year;
    }
}

public class Car : Vehicle
{
    public int DoorCount { get; set; }
    
    public Car(string brand, string model, int year, int doors)
        : base(brand, model, year)
    {
        this.DoorCount = doors;
    }
}

If the base class lacks a parameterless constructor, derived classes must explicitly specify which base constructor to invoke using the base syntax. When no explicit call is provided, the compiler automatically inserts a call to the base parameterless constructor—making it essential to either provide one or make the call explicit.

Access Modifiers and Inheritance

The protected modifier creates a middle ground between public and private access. Protected members are accessible within the class and by derived classes, but remain hidden from other unrelated classes.

Modifier Same Class Derived Classes Other Classes
public Yes Yes Yes
private Yes No No
protected Yes Yes No

Abstract Classes and Methods

Abstract classes serve as blueprints that cannot be instantiated directly. They use the abstract keyword and may contain both implemented members and abstract method declarations.

Abstract methods declare a method signature without implementation, forcing every non-abstract derived class to provide its own implementation using the override keyword.

public abstract class Shape
{
    public string Color { get; set; }
    
    public Shape() { }
    
    public Shape(string color)
    {
        this.Color = color;
    }
    
    // Abstract method - no implementation
    public abstract double CalculateArea();
}

public class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }
    
    public Rectangle(string color, double width, double height)
        : base(color)
    {
        this.Width = width;
        this.Height = height;
    }
    
    public override double CalculateArea()
    {
        return Width * Height;
    }
}

public class Circle : Shape
{
    public double Radius { get; set; }
    
    public Circle(string color, double radius)
        : base(color)
    {
        this.Radius = radius;
    }
    
    public override double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
}

Key points about abstract classes: they cannot be sealed or static, they may contain zero or more abstract methods, and any class containing abstract methods must itself be abstract. Derived non-abstract classes must implement all abstract methods from parent classes.

Polymorphism in Action

Polymorphism allows different objects to respond to the same method call in their own unique ways. This enables writing flexible, extensible code that works with base class types while actually executing derived class implementations.

public static void Main()
{
    Rectangle rect = new Rectangle("Blue", 5, 3);
    Circle circ = new Circle("Red", 4);
    
    List<Shape> shapes = new List<Shape>();
    shapes.Add(rect);
    shapes.Add(circ);
    
    foreach (Shape shape in shapes)
    {
        Console.WriteLine($"Area: {shape.CalculateArea()}");
    }
}

This demonstrates runtime polymorphism—determining which implementation to call happens at execution time based on the actual object type.

Virtual Methods

Virtual methods differ from abstract methods in that they provide a default implementation that derived classes may optionally override. The virtual keyword marks methods as overridable.

public class Employee
{
    public string Name { get; set; }
    public decimal Salary { get; set; }
    
    public Employee(string name, decimal salary)
    {
        this.Name = name;
        this.Salary = salary;
    }
    
    public virtual void Work()
    {
        Console.WriteLine($"{Name} is working");
    }
}

public class Manager : Employee
{
    public int TeamSize { get; set; }
    
    public Manager(string name, decimal salary, int teamSize)
        : base(name, salary)
    {
        this.TeamSize = teamSize;
    }
    
    public override void Work()
    {
        Console.WriteLine($"{Name} is managing a team of {TeamSize} people");
    }
}

public class Developer : Employee
{
    public string Specialty { get; set; }
    
    public Developer(string name, decimal salary, string specialty)
        : base(name, salary)
    {
        this.Specialty = specialty;
    }
}

When a virtual method is not overridden in a derived class, the base class implementation executes. When overridden, the derived class version takes precedence.

Virtual vs Abstract Methods

Aspect Virtual Methods Abstract Methods
Keyword virtual abstract
Implementation Required (at least empty body) Forbidden
Override requirement Optional Mandatory (unles derived class is also abstract)
Placement Any class Abstract class only

Type Checking with is and as

The is operator checks type compatibility at runtime, returning true or false without throwing exceptions.

foreach (Shape item in shapes)
{
    if (item is Rectangle)
        Console.WriteLine("Found a rectangle");
    else if (item is Circle)
        Console.WriteLine("Found a circle");
}

The as operator attempts conversion between compatible reference types, returning null if the conversion fails instead of throwing an exception.

Rectangle rect = item as Rectangle;
if (rect != null)
{
    Console.WriteLine($"Rectangle dimensions: {rect.Width}x{rect.Height}");
}

Method Hiding with new Keyword

When a derived class defines a method with the same signature as a base class method without using override, the new keyword explicitly hides the base implementation. This is termed method hiding or shadowing.

public class Person
{
    public string Name { get; set; }
    
    public void Introduce()
    {
        Console.WriteLine($"Hello, I am {Name}");
    }
}

public class Teacher : Person
{
    public string Subject { get; set; }
    
    public new void Introduce()
    {
        Console.WriteLine($"Hello, I am {Name} and I teach {Subject}");
    }
}

After hiding, instances of Teacher call the derived version, while instances of Person continue using the original implementation. This technique is useful when overriding is not possible (such as with sealed methods) or when intentionally providing a different implementation.

Sealed Classes

The sealed modifier prevents class inheritance. This is valuable for protecting intellectual property, preventing unintended extension, or optimizing performance since the compiler can apply certain optimizations knowing no derived classes exist.

public sealed class ConfigurationManager
{
    private static ConfigurationManager _instance;
    
    private ConfigurationManager() { }
    
    public static ConfigurationManager Instance
    {
        get
        {
            if (_instance == null)
                _instance = new ConfigurationManager();
            return _instance;
        }
    }
}

Attempting to inherit from a sealed class results in a compile-time error.

Object Class Virtual Methods

All C# types ultimately derive from System.Object, which provides several virtual methods worth understanding:

The Equals() method compares object references by default but should be overridden to compare values for custom types. The ToString() method returns the type's fully qualified name by default and is commonly overridden to provide meaningful string representations of objects.

Summary of Key Concepts

Inheritance enables code reuse through parent-child class relationships. Abstract classes provide incomplete blueprints requiring derived class completion. Polymorphism allows identical method calls to produce different behaviors based on actual object type. Virtual methods offer optional overriding with default implementations. The is and as operators facilitate safe type checking and conversion. The new keyword hides base implementations, while sealed prevents inheritance entirely.

Tags: C# Object-Oriented Programming Inheritance Polymorphism abstract classes

Posted on Sun, 30 Aug 2026 16:55:24 +0000 by Baez