Back to Blog
C#

C# Interface Polymorphism Explained with Code Examples

c# interface polymorphism: Learn how C# interface polymorphism enables runtime method dispatch, decouples callers from implementations, and supports flexible, testable...

C# InterfacesPolymorphismOOP DesignDependency InjectionRuntime Dispatch
Diagram showing three different class shapes connected to a central interface node, illustrating C# interface polymorphism

C# interface polymorphism is the ability of different classes to be treated through a common interface type while each implementation supplies its own behavior for the members the interface declares. The compiler allows a variable typed as the interface to hold any object whose class implements that interface, and the runtime dispatches calls to the actual implementation rather than to a fixed method. This separation between what a caller depends on and which concrete class satisfies that contract is what makes interface polymorphism useful in real codebases.

What Interface Polymorphism Requires at the Type Level

For interface polymorphism to work, a class must declare that it implements the interface, and it must provide concrete bodies for every member the interface declares. The interface itself contains only signatures. Consider a simple contract for a payment processor:

public interface IPaymentProcessor { bool ProcessPayment(decimal amount); }

Two classes can implement the same interface with completely different behavior:

public class CreditCardProcessor : IPaymentProcessor { public bool ProcessPayment(decimal amount) { // Charge the card through a gateway. return amount > 0; } } public class PayPalProcessor : IPaymentProcessor { public bool ProcessPayment(decimal amount) { // Redirect to PayPal and wait for confirmation. return amount > 0 && amount < 10000; } }

The caller never needs to know which concrete class it is dealing with. It only needs the interface reference:

IPaymentProcessor processor = GetProcessor(); bool success = processor.ProcessPayment(49.99m);

GetProcessor() can return any implementation. That is the core of interface polymorphism: the same interface reference can point to different implementations at runtime, and the method that actually runs is determined by the object's real type.

How the Runtime Dispatches Interface Calls

When you call a method through an interface reference, the CLR looks up the method in the object's type metadata and invokes the implementation associated with that type. This is a virtual dispatch, similar to what happens when you call a virtual method on a base class. The dispatch is not resolved at compile time because the compiler cannot know which implementation will be present when the code runs.

This runtime resolution has a small cost. Interface dispatch typically involves an extra indirection compared to a direct call on a sealed class. In most application code this cost is negligible, but in tight loops that call interface methods millions of times, the difference can become measurable. If profiling shows that interface dispatch is a bottleneck, you can cache the concrete type in a local variable or restructure the hot path to avoid repeated interface calls.

Interface Polymorphism vs Abstract Class Inheritance

Both interfaces and abstract classes enable polymorphic behavior, but they differ in what they allow you to express. A class can implement many interfaces but can inherit from only one base class. Interfaces also carry no implementation, whereas an abstract class can provide shared implementation for derived classes.

ConcernInterfaceAbstract Class
Multiple inheritanceA class can implement many interfacesA class can inherit from only one abstract class
ImplementationNo implementation allowedCan provide shared implementation
Member kindsMethods, properties, events, indexersFull class member set, including fields and constructors
VersioningAdding a member breaks all implementersAdding a member can be given a default implementation

Choose an interface when the contract is the only thing that matters and implementations have nothing meaningful to share. Choose an abstract class when derived types share state or behavior that should be inherited. Many designs use both: an abstract class implements an interface and provides a partial implementation, leaving specific behavior to derived classes.

Common Mistakes with Interface Polymorphism

One frequent mistake is treating an interface as a way to force unrelated classes into the same shape without considering whether the contract is meaningful. If two classes implement the same interface but the caller must check the concrete type to behave correctly, the interface is not earning its place.

if (processor is CreditCardProcessor cc) { cc.ValidateCardNumber(); }

That kind of type check undermines polymorphism. If the caller needs to know the concrete type, the abstraction is leaking. A better design moves the varying behavior into the interface itself, so the caller can rely on the contract alone.

Another common mistake is ignoring the fact that interface members are public by default and cannot have access modifiers. If you need internal implementation details, they do not belong in the interface. Keep the interface focused on what callers actually need.

Where Interface Polymorphism Shines in Real Code

The most valuable use of interface polymorphism is dependency injection and testing. When a service depends on an interface rather than a concrete class, you can substitute a fake implementation in tests without changing the service's code. This is only possible because the service treats the dependency polymorphically.

public class OrderService { private readonly IPaymentProcessor _processor; public OrderService(IPaymentProcessor processor) { _processor = processor; } public bool PlaceOrder(Order order) { return _processor.ProcessPayment(order.Total); } }

In production you inject a real processor; in tests you inject a fake one. The service does not care which one it receives. This is the same polymorphic mechanism as the earlier example, applied at the dependency boundary.

Maintainability Tradeoffs of Interface Polymorphism

Interface polymorphism improves maintainability when the set of implementations is expected to grow or change independently of the caller. Adding a new payment processor requires no change to the caller. However, adding a new method to the interface forces every implementer to update. That is a real cost, and it grows with the number of implementations.

If you control all implementers and the interface is small, the cost is manageable. If the interface is large or implemented by external parties, consider whether a new interface should be introduced instead of extending the existing one. Interface segregation keeps contracts small and reduces the blast radius of changes.

Performance Considerations for Interface Dispatch

Interface dispatch is not free, but it is rarely the reason a system is slow. The extra indirection matters only in code that runs extremely frequently, such as a per-frame game loop or a high-throughput data pipeline. In those cases, you can often replace an interface call with a direct call by storing the concrete type in a local variable after a single cast, or by using a generic method constrained to the interface, which allows the JIT to devirtualize the call in some scenarios.

Do not optimize interface dispatch preemptively. Measure first. If profiling shows that interface calls are a meaningful fraction of execution time, apply one of the techniques above. Otherwise, keep the interface because the maintainability benefit outweighs the tiny runtime cost.

Choosing the Right Abstraction Level

Interface polymorphism is most effective when the interface represents a stable contract and implementations vary in behavior. If implementations share substantial logic, an abstract class or a base class with virtual methods may serve better. If you only need one implementation and do not expect another, an interface adds indirection without immediate benefit, though it can still be justified if you anticipate future implementations or need test doubles.

The decision comes down to how the code will change. If new implementations are likely, interface polymorphism gives you a clean extension point. If the contract itself is likely to change frequently, keep the interface small and be prepared to update implementers. That tradeoff is inherent to interface-based design, and understanding it is more valuable than memorizing the syntax.

c# interface polymorphism: Practical Usage and Code Examples | RYUSLOG DEV