# Polymorphism with Interfaces

An interface can be a variable's, parameter's or list's type, so it can hold objects of unrelated classes that keep the same contract. Depending on an interface, such as IClock, lets a test pass any fake that implements it.

Freelancer · C# · OOP · Updated November 3, 2026

## After this lesson you can

- Hold objects of different classes in an interface variable or list, and call only the interface's members
- Inject a dependency through an interface, so a test can pass any fake that implements it

## Key takeaways

- An object fits in a variable of any interface its class implements, and the variable's type decides which members you can call.
- Unrelated classes that keep the same contract, such as `Teenager` and `ProAthlete`, fit in one `List<IAthlete>`.
- Ask for a dependency through an interface, such as `IClock`, so the app can pass a `SystemClock` and a test a `FixedClock`.
- In `Program.cs`, `AddScoped<IProductDao>` decides which class keeps the contract that a controller asks for.

In *Changing Roles*, Chloe was a student in class, a singer at rehearsal and an athlete at volleyball. Each role saw only the part of her it needed. Interfaces put those roles into code.

## A role is an interface

Each role becomes an interface, and Chloe's class implements all three:

```csharp
Teenager chloe = new Teenager("Chloe");

IStudent student = chloe;
student.Study();

IAthlete athlete = chloe;
athlete.Train();

public interface IStudent
{
    void Study();
    void TakeTest();
}

public interface ISinger
{
    void Rehearse();
    void Perform();
}

public interface IAthlete
{
    void Train();
    void Compete();
}

public class Teenager : IStudent, ISinger, IAthlete
{
    public string Name { get; }

    public Teenager(string name)
    {
        Name = name;
    }

    public void Study() => Console.WriteLine($"{Name} studies for the math test");
    public void TakeTest() => Console.WriteLine($"{Name} takes the test");
    public void Rehearse() => Console.WriteLine($"{Name} rehearses her lines");
    public void Perform() => Console.WriteLine($"{Name} sings her solo");
    public void Train() => Console.WriteLine($"{Name} practices her serve");
    public void Compete() => Console.WriteLine($"{Name} plays the match");
}
```

```text
Chloe studies for the math test
Chloe practices her serve
```

A `Teenager` **is an** `IStudent`, an `ISinger` and an `IAthlete`, so it can go in a variable of any of those types. And the variable's type decides what you can ask, just as with base classes. Through `student`, the code only knows a student:

```csharp
Teenager chloe = new Teenager("Chloe");

IStudent student = chloe;
student.Train();

public interface IStudent
{
    void Study();
}

public interface IAthlete
{
    void Train();
}

public class Teenager : IStudent, IAthlete
{
    public string Name { get; }

    public Teenager(string name)
    {
        Name = name;
    }

    public void Study() => Console.WriteLine($"{Name} studies");
    public void Train() => Console.WriteLine($"{Name} trains");
}
```

```text
error CS1061: 'IStudent' does not contain a definition for 'Train' and no accessible extension method 'Train' accepting a first argument of type 'IStudent' could be found
```

Chloe can still train. But not every student can, so code that only knows a student can't ask.

## Unrelated classes, one list

Here's what interfaces give you that base classes can't. The volleyball coach needs a team of athletes. Chloe is one, and so is Maya, a professional player who isn't a teenager at all. `ProAthlete` and `Teenager` share no base class, yet both keep the `IAthlete` contract, so both fit in one `List<IAthlete>`:

```csharp
List<IAthlete> team = new List<IAthlete>
{
    new Teenager("Chloe"),
    new ProAthlete("Maya"),
    new Teenager("Sam")
};

foreach (IAthlete player in team)
{
    player.Train();
}

public interface IStudent
{
    void Study();
}

public interface IAthlete
{
    void Train();
}

public class Teenager : IStudent, IAthlete
{
    public string Name { get; }

    public Teenager(string name)
    {
        Name = name;
    }

    public void Study() => Console.WriteLine($"{Name} studies");
    public void Train() => Console.WriteLine($"{Name} practices after school");
}

public class ProAthlete : IAthlete
{
    public string Name { get; }

    public ProAthlete(string name)
    {
        Name = name;
    }

    public void Train() => Console.WriteLine($"{Name} trains with her coach at 6 AM");
}
```

```text
Chloe practices after school
Maya trains with her coach at 6 AM
Sam practices after school
```

The coach's loop doesn't know who studies chemistry or who gets paid to play. It treats everyone as an athlete, and each one trains in their own way. A base class could only gather classes from one family tree; an interface gathers **any class that can do the job**.

## Back to dependency injection

In *Dependency Injection*, the `Greeter` asked for a `Clock` in its constructor, and tests passed a `FixedClock` that **derived** from it. That worked, but it needed two things: `Clock.Hour` had to be `virtual`, and the fake inherited a real clock it never used.

An interface says exactly what the greeter needs, and nothing more: *something that knows the hour.*

```csharp
Greeter morning = new Greeter(new FixedClock(9));
Greeter now = new Greeter(new SystemClock());

Console.WriteLine(morning.Greet("Ada"));
Console.WriteLine(now.Greet("Ada").StartsWith("Good"));

public interface IClock
{
    int Hour { get; }
}

// The real clock, for the app.
public class SystemClock : IClock
{
    public int Hour => DateTime.Now.Hour;
}

// A fake clock, for tests: it's always the hour it was given.
public class FixedClock : IClock
{
    private readonly int hour;

    public FixedClock(int hour)
    {
        this.hour = hour;
    }

    public int Hour => hour;
}

public class Greeter
{
    private readonly IClock clock;

    public Greeter(IClock clock)
    {
        this.clock = clock;
    }

    public string Greet(string name)
    {
        return clock.Hour < 12 ? $"Good morning, {name}!" : $"Good afternoon, {name}!";
    }
}
```

```text
Good morning, Ada!
True
```

`Greeter` now depends on a contract, not on any class. The app passes a `SystemClock`, a test passes a `FixedClock`, and the two classes are equals: neither derives from the other, and nothing needs to be `virtual`.

This is how professional .NET code is written. A controller asks for an `IProductDao`, not a `ProductDao`, and `Program.cs` decides which class keeps that contract:

```csharp
builder.Services.AddScoped<IProductDao>(_ => new ProductDao(connectionString));
```

The controller works with the real DAO in the app, and a test can give it a fake DAO with a few products in a list, with no database at all.

## Check yourself

2 questions to try on the lesson page.

## So what

An interface is a role. Code that asks for a role, an `IAthlete`, an `IClock`, an `IProductDao`, works with every class that can play it: today's, tomorrow's, and the fakes in your tests.

## Key terms

- [Polymorphism](https://www.ask-codey.com/glossary/polymorphism-c-sharp)
- [Dependency Injection](https://www.ask-codey.com/glossary/dependency-injection)

## Cheat sheets

- [Polymorphism with Interfaces](https://www.ask-codey.com/cheat-sheets/interfaces-in-csharp-polymorphism-with-interfaces-cheat-sheet)

Source: https://www.ask-codey.com/lessons/interfaces-in-csharp-polymorphism-with-interfaces
