Inheritance — Extending Classes with Base and Derived Types

Inheritance lets a class (the derived class) acquire all the public and protected members of another class (the base class) and extend or specialise them. It models “is-a” relationships — a BlogPost is a Content, an AdminUser is a User. In ASP.NET Core, every API controller you write inherits from ControllerBase, which is why you can call Ok(), BadRequest(), and NotFound() without implementing them yourself — you inherit them from the framework.

Base and Derived Classes

// ── Base class — shared properties and behaviour ──────────────────────────
public class Content
{
    public int      Id        { get; set; }
    public string   Title     { get; set; } = string.Empty;
    public string   AuthorId  { get; set; } = string.Empty;
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
    public bool     IsPublished { get; set; }

    // Constructor — base classes should also enforce their invariants
    public Content(int id, string title, string authorId)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(title, nameof(title));
        ArgumentException.ThrowIfNullOrWhiteSpace(authorId, nameof(authorId));
        Id       = id;
        Title    = title;
        AuthorId = authorId;
    }

    // Protected constructor — allows derived classes, not external code
    protected Content() { }

    public void Publish() => IsPublished = true;

    public override string ToString() => $"[{Id}] {Title}";
}

// ── Derived class — extends Content with blog-specific data ──────────────
public class BlogPost : Content   // : Content = inherits from Content
{
    public string   Body { get; set; } = string.Empty;
    public string   Slug { get; set; } = string.Empty;
    public string[] Tags { get; set; } = Array.Empty<string>();

    // Call base class constructor with base()
    public BlogPost(int id, string title, string authorId, string body)
        : base(id, title, authorId)   // ← runs Content's constructor first
    {
        Body = body;
        Slug = title.ToLowerInvariant().Replace(" ", "-");
    }

    protected BlogPost() { }  // for EF Core
}

// ── Another derived class ────────────────────────────────────────────────
public class VideoPost : Content
{
    public string VideoUrl    { get; set; } = string.Empty;
    public int    DurationSec { get; set; }

    public VideoPost(int id, string title, string authorId, string videoUrl)
        : base(id, title, authorId)
    {
        VideoUrl = videoUrl;
    }

    protected VideoPost() { }
}
Note: C# only supports single inheritance — a class can only inherit from one base class. This prevents the “diamond problem” of multiple inheritance (ambiguous method resolution when two bases define the same method). However, a class can implement multiple interfaces (Chapter 6), which provides the flexibility of multiple inheritance without the ambiguity. This is the .NET approach: one base class, many interfaces.
Tip: The base() call in a derived constructor must be the first thing that runs — you cannot execute any code before calling the base constructor. If no base() call is specified, C# automatically inserts a call to the parameterless base constructor. If the base class has no parameterless constructor, you must explicitly call one of its parameterised constructors with : base(...), or the code will not compile. This ensures the base class is always fully initialised before the derived class adds its own state.
Warning: Avoid deep inheritance hierarchies (more than 2–3 levels). Deep hierarchies create tight coupling — a change to the base class can break every derived class in unpredictable ways. The industry standard advice is “prefer composition over inheritance” — build objects by combining simpler objects (via constructor injection or properties) rather than inheriting. Use inheritance when the “is-a” relationship is genuinely true and stable; use composition when you want to reuse behaviour without the structural coupling.

What Is and Isn’t Inherited

Member Inherited? Notes
public fields / properties Yes Directly accessible on derived instance
protected fields / properties Yes Accessible inside derived class, not outside
private fields / properties No Exists in base object but inaccessible to derived
public / protected methods Yes Can be called or overridden
Constructors No Must call explicitly with base()
Static members Yes (accessible) Belong to the defining type, not the instance

Common Mistakes

Mistake 1 — Not calling base() when the base class has no parameterless constructor

❌ Wrong — compile error if base has no parameterless constructor:

public class BlogPost : Content
{
    public BlogPost(int id, string title, string authorId, string body)
    {
        // Missing : base(id, title, authorId) — compile error!
    }
}

✅ Correct — always chain to a base constructor explicitly.

Mistake 2 — Trying to access private base members from a derived class

❌ Wrong — private members are not accessible in derived classes:

public class BlogPost : Content
{
    public void DoSomething()
    {
        _privateBaseField = 5;  // compile error — private is not inherited
    }
}

✅ Correct — make the member protected in the base class, or use a public/protected property.

🧠 Test Yourself

When you create a new BlogPost(1, "Hello", "auth1", "Body"), in what order do the constructors run?