Inheritance
Inheritance models is-a relationships: SavingsAccount is an Account.
Introduction
Inheritance models is-a relationships: SavingsAccount is an Account. Derived classes inherit members and can override virtual behavior. C# allows single base class but multiple interfaces — favor composition over deep inheritance hierarchies per modern guidance.
abstract classes provide partial implementations; sealed prevents further derivation. Interviewers ask when inheritance vs interface and problems with fragile base class.
This lesson implements a payment hierarchy used in later polymorphism and abstraction lessons.
The story
A credit union offers savings and checking accounts that share common behavior — every account has a number, accepts deposits, and must define its own withdrawal rules. Savings accounts may block overdrafts entirely, while checking accounts might allow a limited overdraft facility.
Inheritance lets the shared deposit logic live in a base Account class while each account type overrides withdrawal behavior.
Understanding the topic
Key concepts
- Single inheritance: class Derived : Base.
- protected members visible to subclasses.
- virtual methods overridable; override replaces behavior.
- abstract class cannot instantiate; may have abstract methods.
- sealed class blocks inheritance; sealed override blocks further override.
- Composition: has-a via field often preferable to is-a.
classDiagramclass Account {<<abstract>>+decimal Balance+Deposit()}class SavingsAccount {+ApplyInterest()}class CheckingAccount {+OverdraftLimit}Account <|-- SavingsAccountAccount <|-- CheckingAccount
Step-by-step explanation
- Derived constructor must call base constructor.
- Override methods dispatch at runtime on virtual table.
- new keyword hides base member — avoid unless intentional.
- Upcasting: Derived assigned to Base reference.
- Pattern matching on derived types for behavior.
- Interface implementation satisfies contract without inheritance.
Practical code example
Abstract Account with Savings and Checking derivatives:
namespace TechLearningPro.Inheritance;public abstract class Account(string accountNumber){public string AccountNumber { get; } = accountNumber;public decimal Balance { get; protected set; }public void Deposit(decimal amount){if (amount <= 0) throw new ArgumentOutOfRangeException(nameof(amount));Balance += amount;}public abstract bool TryWithdraw(decimal amount);}public sealed class SavingsAccount(string accountNumber) : Account(accountNumber){public override bool TryWithdraw(decimal amount){if (amount <= 0 || amount > Balance) return false;Balance -= amount;return true;}}
Line-by-line code explanation
public abstract class Account(string accountNumber)defines shared structure; it cannot be instantiated directly.public string AccountNumber { get; }stores the identifier common to all account types.public decimal Balance { get; protected set; }allows subclasses to update balance through controlled methods.Deposit(decimal amount)implements shared deposit logic in the base class.if (amount <= 0) throw ...validates deposits once for every account subtype.Balance += amountupdates the protected balance field from the base class.public abstract bool TryWithdraw(decimal amount)forces each subtype to define withdrawal rules.SavingsAccount : Account(accountNumber)inherits from Account and passes the account number upward.override bool TryWithdrawreplaces the abstract method with savings-specific logic.sealed class SavingsAccountprevents further inheritance of this concrete type.
Key takeaway: Abstract TryWithdraw forces subclasses to define rules. protected set on Balance allows subclass mutation through base API.
Real-world use
Where you'll use this in production
- Plugin hierarchies with shared base behavior.
- ASP.NET Identity user extensions.
- UI control inheritance in WPF (legacy).
- Template method pattern in batch processing frameworks.
Best practices
- Prefer shallow hierarchies — max 2-3 levels.
- Favor interfaces and composition for flexibility.
- Seal classes not designed for extension.
- Document Liskov expectations for overrides.
- Use abstract class when shared implementation exists.
Common mistakes
- Deep inheritance trees breaking LSP.
- Using new instead of override — polymorphism breaks silently.
- Inheriting for code reuse only — use composition.
- Calling overridable methods from base constructor.
Advanced interview questions
Q1BeginnerSingle vs multiple inheritance in C#?
Q2Beginnerabstract class vs interface?
Q3Intermediatevirtual vs override vs new?
Q4IntermediateLiskov Substitution Principle?
Q5AdvancedWhen refuse inheritance request from junior dev?
Summary
Inheritance models is-a with single base class. abstract and virtual enable extensible hierarchies. Prefer interfaces and composition for flexibility. Override correctly for runtime polymorphism. Next: polymorphism and dynamic dispatch.