← Back to C# series
🧩
OOP
OOP Β· Prerequisite: polymorphism

10. Interface

Interfaces define only the promise "these methods must exist." They're the key tool for working around single inheritance, doing dependency inversion, and writing testable designs.

C#.NET 8OOPinterface
Duration
⏱ ~1-1.5 hours
Level
πŸ“Š Intermediate
Prerequisite
🎯 Polymorphism
OUTCOME
Interfaces define only the promise "these methods must exist." They're the key tool for working around single inheritance, doing dependency inversion, and writing testable designs.

What you'll learn

  • 1Declare an interface with the `interface` keyword
  • 2Implement an interface in a class (`: IName`)
  • 3Implement multiple interfaces in one class
  • 4Understand default interface members (C# 8+)
  • 5Know when explicit interface implementation is needed

Overview

If inheritance models an "is-a" relationship (a dog is an animal), an **interface** models a "can-do" relationship (this object can print). It defines only the contract β€” **"these methods must exist"** β€” and leaves the implementation to classes. A C# class has only one parent class, but it can implement many interfaces.

Core Concepts

1) Interface declaration and implementation

csharp
interface IPrintable
{
    void Print();   // no body, ends with a semicolon
}

class Book : IPrintable
{
    public string Title = "";
    public void Print() => Console.WriteLine($"Book: {Title}");
}

By convention, interface names start with `I` (`IComparable`, `IDisposable`, ...).

Interfaces cannot have **fields or constructors** by default. (Properties are OK.)

2) Interface = contract

An implementing class must implement every member. Missing any β†’ compile error.

csharp
class Document : IPrintable   // error if Print() is missing
{
    public void Print() => Console.WriteLine("printing document");
}

3) Polymorphism via interface variables

csharp
IPrintable p = new Book();
p.Print();              // runs Book.Print()

You only know **"this variable can call Print"** β€” the concrete class doesn't matter.

4) Implementing multiple interfaces

csharp
class Image : IPrintable, IResizable
{
    public void Print() => Console.WriteLine("printing image");
    public void Resize(int w, int h) => Console.WriteLine($"resized: {w}x{h}");
}

Separate with commas. When mixed with class inheritance, the parent class comes first, then the interfaces.

5) Default interface member (C# 8+)

Interfaces can carry default implementations. If a class doesn't override, the default is used.

csharp
interface ILogger
{
    void Log(string msg);
    void Warn(string msg) => Log($"[WARN] {msg}");   // default implementation
}

Default implementations are **visible only via the interface type**.

csharp
ILogger lg = new ConsoleLogger();
lg.Warn("attention");        // OK
// new ConsoleLogger().Warn("attention");   // compile error (not callable as a regular instance member)

6) Explicit interface implementation

Use when two interfaces have a member with the same name, or when you don't want the method exposed publicly on the class.

csharp
class MyNumber : IComparable<int>
{
    public int Value;

    // Explicit implementation: can't be called directly on class instances
    int IComparable<int>.CompareTo(int other) => Value.CompareTo(other);
}

var n = new MyNumber { Value = 5 };
// n.CompareTo(10);                 // compile error
((IComparable<int>)n).CompareTo(10); // OK β€” cast to the interface, then call

Examples

Example 1 β€” `IPrintable`: one interface, two implementations

csharp
// Program.cs
using CodingNow.Lecture.Oop10;

IPrintable[] items = [new Book("Object-Oriented Reality"), new Invoice(99000)];

foreach (var item in items)
{
    item.Print();
}
csharp
// IPrintable.cs
namespace CodingNow.Lecture.Oop10;

internal interface IPrintable
{
    void Print();
}

// Book.cs
internal class Book : IPrintable
{
    public string Title;
    public Book(string title) => Title = title;
    public void Print() => Console.WriteLine($"Book: {Title}");
}

// Invoice.cs
internal class Invoice : IPrintable
{
    public int Amount;
    public Invoice(int amount) => Amount = amount;
    public void Print() => Console.WriteLine($"Invoice: {Amount} won");
}

**Output**

text
Book: Object-Oriented Reality
Invoice: 99000 won

**Note:** `Book` and `Invoice` aren't related by inheritance β€” they're grouped by the shared capability "can be printed."

Example 2 β€” `MultipleInterfaces`: one class implements many interfaces

csharp
// Program.cs
using CodingNow.Lecture.Oop10;

var img = new Image();
img.Print();
img.Resize(800, 600);
csharp
// Interfaces.cs
namespace CodingNow.Lecture.Oop10;

internal interface IPrintable
{
    void Print();
}

internal interface IResizable
{
    void Resize(int width, int height);
}

// Image.cs
internal class Image : IPrintable, IResizable
{
    public void Print() => Console.WriteLine("printing the image");
    public void Resize(int width, int height)
        => Console.WriteLine($"resizing image to {width}x{height}");
}

**Output**

text
printing the image
resizing image to 800x600

**Note:** Class inheritance is single, but the number of interfaces is unlimited.

Example 3 β€” `DefaultMember`: default interface member

csharp
// Program.cs
using CodingNow.Lecture.Oop10;

ILogger lg = new ConsoleLogger();
lg.Log("normal message");
lg.Warn("this is a warning");        // calls the default implementation

// new ConsoleLogger().Warn(...) is not callable β€” defaults only visible via interface type
csharp
// ILogger.cs
namespace CodingNow.Lecture.Oop10;

internal interface ILogger
{
    void Log(string msg);

    // Default implementation: used if the implementing class doesn't override.
    void Warn(string msg) => Log($"[WARN] {msg}");
}

// ConsoleLogger.cs
internal class ConsoleLogger : ILogger
{
    public void Log(string msg) => Console.WriteLine(msg);
    // Warn not implemented β†’ the default is used
}

**Output**

text
normal message
[WARN] this is a warning

**Note:** When adding a new method to an existing interface, providing a default keeps existing implementing classes from breaking.

Example 4 β€” `ExplicitImpl`: explicit interface implementation

csharp
// Program.cs
using CodingNow.Lecture.Oop10;

var p = new MyPrinter();

// p.Print();   // compile error β€” explicit implementation isn't visible on the class

IPrintable pr = p;
pr.Print();    // OK after casting to the interface

((IPrintable)p).Print();   // equivalent
csharp
// IPrintable.cs
namespace CodingNow.Lecture.Oop10;

internal interface IPrintable
{
    void Print();
}

// MyPrinter.cs
internal class MyPrinter : IPrintable
{
    // Explicit implementation: prefix the method name with "Interface." and no access modifier.
    void IPrintable.Print() => Console.WriteLine("(only callable via interface) printing");
}

**Output**

text
(only callable via interface) printing
(only callable via interface) printing

**Note:** Useful when implementing two interfaces with same-named methods, or when you don't want a method exposed externally.

Full example code (src/)

src/DefaultMember/ConsoleLogger.cs

csharp
namespace CodingNow.Lecture.Oop10;

internal class ConsoleLogger : ILogger
{
    public void Log(string msg) => Console.WriteLine(msg);

    // Warn not implemented β†’ the interface's default is used.
}

src/DefaultMember/DefaultMember.csproj

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.Oop10</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

src/DefaultMember/ILogger.cs

csharp
namespace CodingNow.Lecture.Oop10;

internal interface ILogger
{
    void Log(string msg);

    // default interface member (C# 8+):
    // If a class doesn't separately define Warn, this default is used.
    void Warn(string msg) => Log($"[WARN] {msg}");
}

src/DefaultMember/Program.cs

csharp
using CodingNow.Lecture.Oop10;

ILogger lg = new ConsoleLogger();
lg.Log("normal message");
lg.Warn("this is a warning");   // ConsoleLogger didn't implement Warn β†’ default runs

// new ConsoleLogger().Warn("...");   // default is callable only via the interface type

src/ExplicitImpl/ExplicitImpl.csproj

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.Oop10</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

src/ExplicitImpl/IPrintable.cs

csharp
namespace CodingNow.Lecture.Oop10;

internal interface IPrintable
{
    void Print();
}

src/ExplicitImpl/MyPrinter.cs

csharp
namespace CodingNow.Lecture.Oop10;

internal class MyPrinter : IPrintable
{
    // Explicit implementation: prefix with "Interface.Name" and omit the access modifier.
    // This way it isn't exposed as a regular method on the class.
    void IPrintable.Print() => Console.WriteLine("(only callable via interface) printing");
}

src/ExplicitImpl/Program.cs

csharp
using CodingNow.Lecture.Oop10;

var p = new MyPrinter();

// p.Print();   // explicit impl β†’ not visible from the class (uncomment -> compile error)

// 1) Receive via interface variable, then call
IPrintable pr = p;
pr.Print();

// 2) Inline cast to the interface, then call
((IPrintable)p).Print();

src/IPrintable/Book.cs

csharp
namespace CodingNow.Lecture.Oop10;

internal class Book : IPrintable
{
    public string Title;

    public Book(string title) => Title = title;

    public void Print() => Console.WriteLine($"Book: {Title}");
}

src/IPrintable/IPrintable.cs

csharp
namespace CodingNow.Lecture.Oop10;

// Convention: interface names start with I.
internal interface IPrintable
{
    void Print();
}

src/IPrintable/IPrintable.csproj

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.Oop10</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

src/IPrintable/Invoice.cs

csharp
namespace CodingNow.Lecture.Oop10;

internal class Invoice : IPrintable
{
    public int Amount;

    public Invoice(int amount) => Amount = amount;

    public void Print() => Console.WriteLine($"Invoice: {Amount} won");
}

src/IPrintable/Program.cs

csharp
using CodingNow.Lecture.Oop10;

// Even without an inheritance relationship, classes implementing the same interface can be handled together.
IPrintable[] items = [new Book("Object-Oriented Reality"), new Invoice(99000)];

foreach (var item in items)
{
    item.Print();
}

src/MultipleInterfaces/Image.cs

csharp
namespace CodingNow.Lecture.Oop10;

// A single class can implement multiple interfaces.
internal class Image : IPrintable, IResizable
{
    public void Print() => Console.WriteLine("printing the image");

    public void Resize(int width, int height)
        => Console.WriteLine($"resizing image to {width}x{height}");
}

src/MultipleInterfaces/Interfaces.cs

csharp
namespace CodingNow.Lecture.Oop10;

internal interface IPrintable
{
    void Print();
}

internal interface IResizable
{
    void Resize(int width, int height);
}

src/MultipleInterfaces/MultipleInterfaces.csproj

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.Oop10</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

src/MultipleInterfaces/Program.cs

csharp
using CodingNow.Lecture.Oop10;

var img = new Image();
img.Print();           // capability from IPrintable
img.Resize(800, 600);  // capability from IResizable

Common Mistakes

  1. Adding `public` to interface members β€” all members are implicitly public; explicit `public` is a compile error (with some C# 8+ exceptions).
  2. Omitting `public` on the implementing class β€” the class side must declare `public` (explicit implementation is the exception).
  3. Trying to add fields to an interface β€” interfaces have no fields, only properties.
  4. Trying to call an explicitly-implemented method directly on the class instance β†’ compile error. You need to cast to the interface.
  5. Not knowing that interfaces can inherit from other interfaces with `interface IFoo : IBar` (extension interface pattern).

Summary

  • An interface is a contract of "what can be done." No method bodies, no fields.
  • A class can implement many interfaces β€” a safe alternative to multiple class inheritance.
  • Polymorphism works naturally via interface variables.
  • Default implementations (C# 8+) and explicit implementations solve conflict and evolution scenarios.

Practice

**Practice - 10. Interface**

Problem 1 β€” Sort by implementing `IComparable<T>`

  • Project folder: `Homework01/`
  • Key concepts: implementing a standard-library interface, using built-in sort

Requirements

  • Create a `Student` class. Fields: `Name`(string), `Score`(int).
  • Implement `IComparable<Student>` so that `CompareTo` compares **by score, descending**.
  • Add 3-4 students to a `List<Student>`, call `Sort()`, and print.

Expected output

text
Yeonghee: 95
Cheolsu: 80
Minsu: 70

Hints

  • `int.CompareTo(other)` returns negative if less, 0 if equal, positive if greater.
  • For descending order, swap the comparison: `other.Score.CompareTo(this.Score)`.
  • `using System;` is included by `ImplicitUsings` so you don't need to add it explicitly.

Problem 2 β€” Implement `IDrawable` + `IResizable`

  • Project folder: `Homework02/`
  • Key concepts: implementing multiple interfaces, calling via interface variable

Requirements

  • `IDrawable` interface: `void Draw()`.
  • `IResizable` interface: `void Resize(int width, int height)`.
  • Class `Rectangle` implements both; has width/height fields.
  • `Draw()` prints the current size; `Resize` changes size and prints a message.
  • In `Program.cs` create a `Rectangle` and call both methods.

Expected output

text
Drawing rectangle (10x5)
Rectangle resized β†’ (20x10)
Drawing rectangle (20x10)

Hints

  • List as `class Rectangle : IDrawable, IResizable` (comma-separated).
  • Both interface methods must be implemented as `public`.

Check your answer

Try it yourself, then compare against the [`answer/`](./answer/) folder.

Answer (answer/)

homework/answer/Homework01/Homework01.csproj

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.Oop10</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

homework/answer/Homework01/Program.cs

csharp
using CodingNow.Lecture.Oop10;

var students = new List<Student>
{
    new Student("Cheolsu", 80),
    new Student("Yeonghee", 95),
    new Student("Minsu", 70),
};

students.Sort();   // IComparable<Student>.CompareTo runs (descending by score)

foreach (var s in students)
{
    Console.WriteLine($"{s.Name}: {s.Score}");
}

homework/answer/Homework01/Student.cs

csharp
namespace CodingNow.Lecture.Oop10;

internal class Student : IComparable<Student>
{
    public string Name;
    public int Score;

    public Student(string name, int score)
    {
        Name = name;
        Score = score;
    }

    // Descending by score: swap this and other in the comparison.
    public int CompareTo(Student? other)
    {
        if (other is null) return 1;
        return other.Score.CompareTo(this.Score);
    }
}

homework/answer/Homework02/Homework02.csproj

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <RootNamespace>CodingNow.Lecture.Oop10</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

homework/answer/Homework02/Interfaces.cs

csharp
namespace CodingNow.Lecture.Oop10;

internal interface IDrawable
{
    void Draw();
}

internal interface IResizable
{
    void Resize(int width, int height);
}

homework/answer/Homework02/Program.cs

csharp
using CodingNow.Lecture.Oop10;

var rect = new Rectangle(10, 5);
rect.Draw();
rect.Resize(20, 10);
rect.Draw();

homework/answer/Homework02/Rectangle.cs

csharp
namespace CodingNow.Lecture.Oop10;

internal class Rectangle : IDrawable, IResizable
{
    public int Width;
    public int Height;

    public Rectangle(int width, int height)
    {
        Width = width;
        Height = height;
    }

    public void Draw() => Console.WriteLine($"Drawing rectangle ({Width}x{Height})");

    public void Resize(int width, int height)
    {
        Width = width;
        Height = height;
        Console.WriteLine($"Rectangle resized β†’ ({Width}x{Height})");
    }
}

Try It Yourself

bash
cd src/IPrintable
dotnet run

cd ../MultipleInterfaces
dotnet run

cd ../DefaultMember
dotnet run

cd ../ExplicitImpl
dotnet run

Next Lecture

[11_Array](../../03_%EC%BB%AC%EB%A0%89%EC%85%98_LINQ/11_%EB%B0%B0%EC%97%B4/) β€” The most basic collection: an array of same-typed values.

Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub β†—