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.