Composition vs Inheritance in C#: When to Use Each
Inheritance says "I am a..." — Composition says "I have a..."
Imagine you're building a car. You could use inheritance: "A Car is a Vehicle" — that works. But a Car also has an engine, wheels, and a steering wheel. If you tried to model those with inheritance, you'd need a messy hierarchy: "Car is a Engine"? No, that's nonsense.
Inheritance is a powerful tool, but it's often overused. When you inherit, you tie your subclass tightly to the parent. That's fine for "is-a" relationships (like Dog is an Animal), but for "has-a" relationships (like a Car has an Engine), composition is the better choice.
In this lesson, you'll learn when to use inheritance, when to use composition, and why composition is often preferred in modern software design.
What Is It?
The Simple Explanation
Inheritance is the "is-a" relationship. A Dog is an Animal. A Square is a Shape. The subclass inherits behavior from the base class.
Composition is the "has-a" relationship. A Car has an Engine. A House has a Door. The class contains instances of other classes to provide functionality.
The Technical Definition
Inheritance (in OOP) is a mechanism where a class derives from another class, gaining its members and behavior. In C#, inheritance is single (a class can inherit from only one base class) but allows method overriding and polymorphism.
Composition is a design principle where a class contains objects of other classes as fields or properties. It delegates work to those contained objects, effectively "composing" behavior from multiple parts.
Inheritance (is-a)
- Creates a hierarchy of types
- Subclass reuses parent code
- Can override behavior (
virtual/override) - Tight coupling — subclass depends on parent
- Fragile base class problem
- Single inheritance in C#
Composition (has-a)
- Builds objects from parts
- Class uses other objects via delegation
- Behavior can be changed at runtime by swapping parts
- Loose coupling — parts are independent
- More flexible and easier to test
- Not limited by inheritance constraints
Why Does It Exist?
The Problem with Overusing Inheritance
Inheritance sounds great — "reuse code!" — but it comes with hidden costs:
- Fragile Base Class — changes to the base class can break all subclasses, even if you didn't touch them.
- Inflexibility — once you inherit, you're stuck with the parent's behavior unless you override it (which can be complex).
- Deep Hierarchies — they become hard to understand and maintain.
- Cannot change at runtime — the relationship is fixed at compile time.
- Inheritance is limited to one base class in C#, so you can't combine multiple behaviors.
The Solution — Prefer Composition
Composition solves these issues:
- Loose coupling — components are independent and can be changed without affecting each other.
- Flexibility — you can swap parts at runtime (e.g., change the engine of a car).
- Reusability — you can compose behavior from many different objects.
- No deep hierarchies — your classes remain flat and simple.
- Easier testing — you can mock the parts.
The principle "Favor composition over inheritance" is one of the most important design guidelines in object-oriented programming.
Big Picture
Animal ← Mammal ← Dog
Car ⤳ EngineCar ⤳ WheelCar ⤳ SteeringWheel
How It Works
public class Animal
{
public virtual void Speak() => Console.WriteLine("...");
}
public class Dog : Animal
{
public override void Speak() => Console.WriteLine("Woof!");
}
Doginherits all members ofAnimal- Can override virtual members
Animal animal = new Dog();
animal.Speak(); // "Woof!"
public class Engine
{
public void Start() => Console.WriteLine("Engine started");
}
public class Car
{
private Engine _engine = new Engine();
public void Start() => _engine.Start();
}
Carcontains anEngine(has-a)- Delegates work to the engine
- You can pass a different engine to the constructor
- You can replace the engine at runtime (if designed that way)
- No inheritance chain to break
Simple Example
Inheritance — "is-a"
public class Shape
{
public virtual double Area() => 0;
}
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public override double Area() => Width * Height;
}
public class Circle : Shape
{
public double Radius { get; set; }
public override double Area() => Math.PI * Radius * Radius;
}
Meaning: A Rectangle is a Shape. Circle is a Shape. They share a common base and can be used polymorphically.
Composition — "has-a"
public class Logger
{
public void Log(string message) => Console.WriteLine($"Log: {message}");
}
public class OrderService
{
private Logger _logger; // composition
public OrderService(Logger logger) => _logger = logger;
public void ProcessOrder() => _logger.Log("Order processed");
}
Meaning: OrderService has a Logger. It doesn't inherit from Logger; it uses it. This makes OrderService flexible — you can swap loggers or mock them for testing.
Real-World Example
Consider a notification system that can send emails, SMS, and push notifications. With inheritance, you might create a base Notifier and derive EmailNotifier, SmsNotifier, etc. But if you need to combine behaviors (e.g., email + SMS), inheritance fails.
With composition, you can create small, focused classes and combine them flexibly.
// ─── Parts (composition) ───
public interface IMessageSender
{
void Send(string message);
}
public class EmailSender : IMessageSender
{
public void Send(string message) => Console.WriteLine($"Email: {message}");
}
public class SmsSender : IMessageSender
{
public void Send(string message) => Console.WriteLine($"SMS: {message}");
}
public class PushSender : IMessageSender
{
public void Send(string message) => Console.WriteLine($"Push: {message}");
}
// ─── Composed Notification Service ───
public class NotificationService
{
private readonly List<IMessageSender> _senders;
public NotificationService(params IMessageSender[] senders)
{
_senders = senders.ToList();
}
public void Notify(string message)
{
foreach (var sender in _senders)
sender.Send(message);
}
}
// ─── Usage ───
var service = new NotificationService(
new EmailSender(),
new SmsSender()
);
service.Notify("Order shipped!"); // Sends both email and SMS
Why this is better than inheritance:
- You can compose any combination of senders at runtime.
- Adding a new sender (e.g.,
SlackSender) doesn't require changing existing code. - Each sender is focused and testable in isolation.
- No deep inheritance hierarchies — just interfaces and composition.
Analogy
Inheritance is like a pre‑assembled toy — you get a specific shape, and you can't easily change the parts. If you want a different toy, you have to buy a new one (create a new subclass). The structure is fixed.
Composition is like LEGO bricks — you have small, reusable pieces that you can combine in countless ways. You can swap a red brick for a blue one, or add a wheel to a car. The possibilities are endless, and you can change the configuration at any time.
In software, composition gives you the same flexibility: you build complex behavior by combining small, focused objects, rather than locking yourself into a rigid hierarchy.
Under the Hood
- The derived class object contains the base class sub‑object.
- Virtual methods are dispatched via the vtable.
- Memory includes all base class fields plus derived fields.
- Calling a base method is a direct call if not overridden.
- The containing class holds references to component objects.
- No automatic inheritance of members; you must explicitly delegate.
- Memory is separate objects; no base class overhead.
- Can use dependency injection to supply components.
- Inheritance method calls may be slightly faster (direct vtable lookup).
- Composition involves delegation, which adds a small overhead, but it's negligible in most cases.
- Composition often leads to more objects, which can increase memory usage, but the design flexibility usually outweighs that.
Common Confusion
1. "Composition is just using objects" — yes, but it's a design principle
Every class that uses another object is using composition. The principle is about preferring composition over inheritance when designing relationships. It's not just a tactic; it's a mindset to favor loose coupling.
2. "Inheritance is always bad" — no, it has its place
Inheritance is great for genuine "is-a" relationships where the behavior is truly shared and unlikely to change. For example, a List<T> inherits from Collection<T> because a list is a collection. Use inheritance when the relationship is stable and you want to reuse code and enable polymorphism.
3. "Composition is always more code" — yes, but it's worth it
Composition often requires writing more code (delegating methods), but the code is simpler, more testable, and more maintainable. The extra lines are a small price for flexibility.
Common Mistakes
Mistake 1 — Forcing inheritance for convenience
Creating a base class just to share a few helper methods, even when there's no logical "is-a" relationship.
public class UtilityBase
{
public void Log(string msg) { ... }
}
public class OrderService : UtilityBase { } // Not an "is-a"
Use composition: inject a logger instead.
Mistake 2 — Over-engineering with composition
Composing everything into tiny pieces, making the design overly complex for simple problems.
Use the right tool for the job. For trivial cases, a simple inheritance might be fine.
Mistake 3 — Not using interfaces with composition
Hard-coding concrete components inside a class, making it hard to swap or test.
public class OrderService
{
private Logger _logger = new Logger(); // tight coupling
}
Depend on an interface: private readonly ILogger _logger; and inject it.
When Should I Use It?
Use inheritance when:
- There is a clear, stable "is-a" relationship.
- You want to reuse code and override behavior.
- You need runtime polymorphism (virtual dispatch).
- The relationship is unlikely to change.
- You are designing a framework where base classes provide default behavior.
Use composition when:
- There is a "has-a" relationship (which is most of the time).
- You need flexibility to change behavior at runtime.
- You want to keep classes small and focused.
- You want to enable unit testing with mocks.
- You need to combine behavior from multiple sources (multiple inheritance of behavior).
Mental Model
Composition = "I have a..."
Remember:
· Inheritance creates a tight bond — changes to parent affect children.
· Composition creates loose coupling — parts can be swapped independently.
· Favor composition over inheritance, except when the relationship is truly an "is-a" and stable.
· Composition leads to more maintainable and testable code.
Key Takeaway
- Inheritance is "is-a" — a Dog is an Animal. It's good for sharing common behavior and enabling polymorphism, but it creates tight coupling.
- Composition is "has-a" — a Car has an Engine. It's more flexible, promotes loose coupling, and is easier to test.
- Favor composition over inheritance — this principle leads to more maintainable and flexible systems.
- Use inheritance when the relationship is stable and truly hierarchical. Use composition for everything else.
- Both have their place — the key is knowing when to use which.
Check Your Understanding
You've learned the difference between inheritance and composition, and when to use each. Let's see if you can apply these concepts.
1. Which of the following is an example of an "is-a" relationship, where inheritance is appropriate?
Show answer
Correct: B
Why B is correct: A Square is a Shape — this is a classic inheritance relationship. The behavior and properties of a Shape can be shared and overridden by Square.
Why A, C, D are incorrect: All of these are "has-a" relationships. A Car has an Engine, OrderService has a Logger, House has a Door. These are better modeled with composition.
Reinforcement: Inheritance models "is-a"; composition models "has-a".
2. What is a major drawback of using inheritance excessively?
Show answer
Correct: B
Why B is correct: Inheritance couples the subclass to the base class. Changes to the base class can break subclasses unexpectedly (fragile base class problem). This is one of the main reasons to prefer composition.
Why A is incorrect: Inheritance actually promotes code reuse (that's one of its purposes).
Why C is incorrect: Inheritance generally has minimal performance impact; method calls are fast.
Why D is incorrect: Inheritance and interfaces can be used together; they are not mutually exclusive.
Reinforcement: Tight coupling and fragility are the primary drawbacks of overusing inheritance.
3. Which of the following best describes the principle "Favor composition over inheritance"?
Show answer
Correct: B
Why B is correct: The principle is a guideline, not an absolute rule. It suggests that composition often leads to more flexible and maintainable designs, but inheritance still has its place for "is-a" relationships.
Why A is incorrect: It says "favor," not "always." Inheritance is still useful in appropriate scenarios.
Why C is incorrect: Inheritance and interfaces are different concepts; the principle applies to class inheritance.
Why D is incorrect: Composition is a general design principle, not limited to any specific domain.
Reinforcement: Favor composition means prefer it when it makes sense, not that it's always the only choice.
4. You are building a PaymentProcessor that needs to log each transaction. You already have a ILogger interface with multiple implementations (ConsoleLogger, FileLogger). Which approach is better?
Show answer
Correct: C
Why C is correct: PaymentProcessor has a logger (composition). Injecting the logger via the constructor allows you to swap implementations easily, and makes testing simpler (you can mock ILogger). This follows the dependency inversion principle.
Why A and B are incorrect: PaymentProcessor is not a type of logger; inheriting would be a misuse of inheritance (not an "is-a" relationship).
Why D is incorrect: Copy-pasting code is a maintenance nightmare and violates DRY.
Reinforcement: For cross-cutting concerns like logging, composition (dependency injection) is the standard pattern.
5. Which of the following is a benefit of composition over inheritance?
Show answer
Correct: B
Why B is correct: With composition, you can swap out components (e.g., change the engine of a car) at runtime, which is not possible with inheritance (the relationship is fixed at compile time).
Why A is incorrect: Composition does not provide multiple inheritance of classes (C# still has single inheritance), but it does allow combining behavior from many objects, which is a form of multiple inheritance of behavior.
Why C is incorrect: Interfaces are often used in conjunction with composition to define contracts for the parts.
Why D is incorrect: Inheritance method calls can be slightly faster, but the difference is negligible; flexibility is the primary benefit.
Reinforcement: Runtime flexibility is one of the biggest advantages of composition.
You now understand the trade-offs between inheritance and composition — you can design more flexible, maintainable systems!