Back to Blog
C#

Multiple Inheritance in C#: What to Use Instead

c# multiple inheritance in c#: C# does not support multiple inheritance. Learn why, and how to use interfaces, composition, default interface methods, and extension me...

C# inheritanceinterfacescompositiondefault interface methodsextension methodsdiamond problem
Diagram showing a single inheritance chain in C# with multiple interface blocks attached to one class node, illustrating the alternative to multiple inheritance

c# multiple inheritance in c# requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

Multiple Inheritance in C#: The Core Restriction

Multiple inheritance in C# is not available: a class can inherit from exactly one base class, and that base class can itself inherit from one class, forming a single chain. The class declaration syntax makes this explicit:

public class Derived : BaseClass { }

There is no syntax for class Derived : BaseClass1, BaseClass2. The compiler rejects any attempt to list more than one base class. This is a deliberate design decision in the language and the runtime, not a missing feature that was never implemented.

The restriction exists because multiple inheritance creates real problems in object-oriented languages. The most well-known is the diamond problem.

Why the Diamond Problem Drives the Design

The diamond problem occurs when a class inherits from two classes that themselves share a common ancestor. Consider this hypothetical hierarchy:

// Hypothetical C# - does not compile public class Base { public virtual void Run() { } } public class Left : Base { public override void Run() { } } public class Right : Base { public override void Run() { } } public class Bottom : Left, Right // invalid { }

Now Bottom inherits two different implementations of Run(). Which one should a call to bottom.Run() resolve to? The language would need a rule for resolving conflicting members, and every rule creates surprising behavior in some corner case. C++ allows this and requires explicit qualification. C# avoids the problem entirely by allowing only one base class.

There is also a runtime cost concern. The Common Language Runtime (CLR) lays out object fields in a predictable order for single inheritance. Multiple inheritance would complicate field layout, virtual method dispatch, and casting operations. The CLR's design assumes a single inheritance chain for classes.

Interfaces: The Supported Way to Share Contracts

C# provides interfaces as the sanctioned mechanism for a type to satisfy multiple contracts. A class can implement any number of interfaces while still inheriting from exactly one base class:

public interface ILogger { void Log(string message); } public interface IMetrics { void Record(string name, double value); } public class Service : BaseService, ILogger, IMetrics { public void Log(string message) { // write to log } public void Record(string name, double value) { // write to metrics store } }

This gives the type checking benefits of multiple inheritance without the ambiguity. A Service instance can be passed anywhere an ILogger or IMetrics is expected, and the compiler knows exactly which members exist on the concrete type.

The key difference from multiple inheritance is that interfaces carry no state and no implementation. They define a contract. If two interfaces declare a member with the same signature, the class must provide an implementation that satisfies both, and that implementation is shared unless explicit interface implementation is used.

Default Interface Methods: Implementation Without State

C# 8 introduced default interface methods. An interface can now provide a body for its members:

public interface ILogger { void Log(string message); void LogError(string message) { Log($"ERROR: {message}"); } }

A class that implements ILogger must provide Log, but it inherits the default LogError implementation unless it overrides it. This allows a form of implementation sharing across unrelated classes.

The limitation is that default interface methods cannot access instance state. They can only call other interface members, use static fields, or work with the interface's own members. There is no backing field in an interface. So this is not a substitute for inheriting implementation logic that depends on private fields.

Default interface methods are most useful when you want to add a member to a widely implemented interface without breaking existing implementers. They are less useful as a general multiple-inheritance replacement because they cannot hold state.

Composition: The Practical Replacement

When developers search for multiple inheritance in C#, they usually want to reuse behavior from more than one source. Composition achieves that without inheritance at all. Instead of inheriting, the class holds instances of the behaviors it needs and delegates to them:

public interface ILogger { void Log(string message); } public class FileLogger : ILogger { public void Log(string message) { // append to file } } public class Service { private readonly ILogger _logger; public Service(ILogger logger) { _logger = logger; } public void Process() { _logger.Log("Processing started"); } }

The Service class does not inherit from FileLogger. It receives a logger through its constructor and delegates to it. This keeps the dependency explicit, testable, and replaceable. If the logging implementation changes, the Service class does not change.

Composition has a practical advantage over inheritance: the relationship is visible at the call site. A reader of Service can see that it uses an ILogger. With inheritance, the source of a method may be several levels up the hierarchy, and the reader must trace the chain to understand behavior.

Mixin-Style Reuse with Extension Methods

Extension methods provide another way to share behavior without inheritance. A static class can define methods that appear on any type implementing a specific interface:

public static class LoggerExtensions { public static void LogError(this ILogger logger, string message) { logger.Log($"ERROR: {message}"); } }

Now any ILogger instance has a LogError method available, even though the interface never declared it. This is a compile-time convenience: the extension method is just a static call that the compiler rewrites.

Extension methods cannot access private state, cannot be virtual, and are not part of the type's interface. They are resolved at compile time based on the static type of the receiver. They work well for shared utility behavior, but they do not participate in polymorphism. A caller using the concrete type sees the extension method; a caller using the interface sees it only if the static class is imported.

Choosing Between the Approaches

The decision between interfaces, default methods, composition, and extension methods depends on what you need to share:

NeedRecommended approach
Type contract that multiple classes satisfyInterface
Shared implementation without stateDefault interface method
Shared implementation with private stateComposition
Utility behavior on an existing interfaceExtension method
Polymorphic behavior with virtual dispatchSingle inheritance chain

Use an interface when the important thing is that a type can be used where a contract is expected. Use composition when the shared behavior has state or dependencies that must be configured. Use default interface methods when you need to evolve an interface without breaking implementers. Use extension methods for stateless conveniences.

The common mistake is forcing a class hierarchy to simulate multiple inheritance. If two unrelated classes need the same logging behavior, inheriting from a common base class couples them in a way that composition avoids. The base class becomes a dumping ground for shared code, and every new subclass inherits everything the base provides, whether it needs it or not.

Maintainability and Runtime Behavior

Single inheritance keeps the type hierarchy predictable. Field layout is deterministic, virtual dispatch follows a single chain, and casting between base and derived types is unambiguous. These properties matter in large codebases where a type's behavior is inspected through reflection, serialization, or debugging tools.

Composition changes the runtime picture in one important way: the composed dependencies are ordinary objects with their own lifetimes. A class that holds an ILogger must decide who creates that logger, who disposes it, and how it is shared across instances. These are lifecycle questions that inheritance hides. In a dependency-injection setup, the container typically resolves the logger and the class never constructs it directly. That is usually the right design, but it means the class's behavior is no longer fully described by its inheritance chain.

Default interface methods also have a subtle runtime consideration. When a class implements an interface with a default method, the default implementation is compiled into the interface's type, not into the class. Calls to the default method dispatch through the interface. If the class overrides the method, the override is used. This is efficient, but it means the default method is not available on the concrete type unless the type is used through the interface.

A Practical Example Combining the Approaches

A realistic service often needs logging, validation, and metrics. None of these should be inherited from a common base class. Here is a combined pattern:

public interface ILogger { void Log(string message); } public interface IValidator { bool Validate(object input); } public class OrderService { private readonly ILogger _logger; private readonly IValidator _validator; public OrderService(ILogger logger, IValidator validator) { _logger = logger; _validator = validator; } public void Submit(Order order) { if (!_validator.Validate(order)) { _logger.Log("Order validation failed"); return; } _logger.Log("Order submitted"); } }

The OrderService implements no interfaces. It depends on two abstractions and delegates to them. This is the pattern that most C# codebases settle on for cross-cutting concerns. It gives the flexibility that multiple inheritance would provide, without the ambiguity, and it keeps each dependency replaceable.

The tradeoff is that OrderService now has more constructor parameters and more moving parts. That is acceptable when the dependencies are genuinely independent. When a dependency is trivial and stateless, an extension method or a default interface method may be simpler.

c# multiple inheritance in c#: Practical Usage and Code Exam | RYUSLOG DEV