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.
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
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.
class Document : IPrintable // error if Print() is missing
{
public void Print() => Console.WriteLine("printing document");
}3) Polymorphism via interface variables
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
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.
interface ILogger
{
void Log(string msg);
void Warn(string msg) => Log($"[WARN] {msg}"); // default implementation
}Default implementations are **visible only via the interface type**.
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.
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 callExamples
Example 1 β `IPrintable`: one interface, two implementations
// Program.cs
using CodingNow.Lecture.Oop10;
IPrintable[] items = [new Book("Object-Oriented Reality"), new Invoice(99000)];
foreach (var item in items)
{
item.Print();
}// 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**
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
// Program.cs
using CodingNow.Lecture.Oop10;
var img = new Image();
img.Print();
img.Resize(800, 600);// 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**
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
// 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// 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**
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
// 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// 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**
(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
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
<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
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
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
<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
namespace CodingNow.Lecture.Oop10;
internal interface IPrintable
{
void Print();
}
src/ExplicitImpl/MyPrinter.cs
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
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
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
namespace CodingNow.Lecture.Oop10;
// Convention: interface names start with I.
internal interface IPrintable
{
void Print();
}
src/IPrintable/IPrintable.csproj
<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
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
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
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
namespace CodingNow.Lecture.Oop10;
internal interface IPrintable
{
void Print();
}
internal interface IResizable
{
void Resize(int width, int height);
}
src/MultipleInterfaces/MultipleInterfaces.csproj
<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
using CodingNow.Lecture.Oop10;
var img = new Image();
img.Print(); // capability from IPrintable
img.Resize(800, 600); // capability from IResizable
Common Mistakes
- Adding `public` to interface members β all members are implicitly public; explicit `public` is a compile error (with some C# 8+ exceptions).
- Omitting `public` on the implementing class β the class side must declare `public` (explicit implementation is the exception).
- Trying to add fields to an interface β interfaces have no fields, only properties.
- Trying to call an explicitly-implemented method directly on the class instance β compile error. You need to cast to the interface.
- 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
Yeonghee: 95
Cheolsu: 80
Minsu: 70Hints
- `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
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
<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
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
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
<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
namespace CodingNow.Lecture.Oop10;
internal interface IDrawable
{
void Draw();
}
internal interface IResizable
{
void Resize(int width, int height);
}
homework/answer/Homework02/Program.cs
using CodingNow.Lecture.Oop10;
var rect = new Rectangle(10, 5);
rect.Draw();
rect.Resize(20, 10);
rect.Draw();
homework/answer/Homework02/Rectangle.cs
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
cd src/IPrintable
dotnet run
cd ../MultipleInterfaces
dotnet run
cd ../DefaultMember
dotnet run
cd ../ExplicitImpl
dotnet runNext 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.
All lecture materials and example code are openly available on GitHub.
View on GitHub β