C# (.NET 8 LTS) track

Lesson 10 · Part 2: Object-Oriented Programming

Interfaces

Define contracts with interfaces, implement several at once and program against abstractions.

Part 2: Object-Oriented Programming5 min readFree lesson

If inheritance is an "is-a" relationship (a dog is an animal), interface is a "can-do" relationship (this object can print). It simply presents a contract saying “there must be a method like this” without any data or body, and leaves the implementation to the class. A C# class can have only one parent, but can implement multiple interfaces simultaneously.

Learning Objectives

  • You can declare an interface with the interface keyword.
  • A class can implement an interface (: IName)
  • One class can implement multiple interfaces simultaneously
  • Understand the default implementation (default interface member, C# 8+)
  • Know situations where explicit interface implementation is needed

Key concepts

1) Interface declaration and implementation

interface IPrintable
{
    void Print();   //No body, ends with semicolon
}

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

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

field/constructor cannot be placed on an interface by default. (property is OK)

2) Interface = Promise

The implementing class must implement all members. Compilation error if missing.

class Document : IPrintable   //Error if Print() is not created
{
    public void Print() => Console.WriteLine("document output");
}

3) Polymorphism with interface variables

IPrintable p = new Book();
p.Print();              //Execute Book.Print()

"This variable only guarantees that Print can be called". Any class doesn't matter.

4) Implementing multiple interfaces

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

Listed with commas. When used with class inheritance, the parent class comes first, then the interfaces.

5) Default implementation (default interface member, C# 8+)

You can also give interfaces a default implementation. Even if the implementation class does not override it, the default behavior is invoked.

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

However, the default implementation is only visible when called as an interface type.

ILogger lg = new ConsoleLogger();
lg.Warn("caution");        // OK
//new ConsoleLogger().Warn("Warning");   // Compilation error (cannot be called as an instance member)

6) Explicit interface implementation

Used when two interfaces have members with the same name or when you do not want to expose the method directly to the outside world.

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

    //Explicit implementation: cannot be called directly from a class instance
    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 — call after casting to interface

See as example

Example 1 — IPrintable: one interface, two implementations

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

IPrintable[] items = [new Book("Facts and misconceptions about object orientation"), 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($"Bill: {Amount} Won");
}

Execution result

Book: Object-Oriented Facts and Myths
Bill: 99,000 won

Note: Even though Book and Invoice are not parent-child, they are grouped together with the common ability of “Can Print.”

Example 2 — MultipleInterfaces: One class implements multiple 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("Print the image");
    public void Resize(int width, int height)
        => Console.WriteLine($"Change image size to {width}x{height}");
}

Execution result

Print the image
Change image size to 800x600

Note: Classes are single-inherited, but there is no limit to the number of interfaces.

Example 3 — DefaultMember: Interface default implementation

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

ILogger lg = new ConsoleLogger();
lg.Log("normal message");
lg.Warn("This is a warning");        //Default implementation is called

//new ConsoleLogger().Warn(...) cannot be called — the default implementation appears only as an interface type
// ILogger.cs
namespace CodingNow.Lecture.Oop10;

internal interface ILogger
{
    void Log(string msg);

    //Default implementation: This behavior is used unless the implementing class overrides it.
    void Warn(string msg) => Log($"[WARN] {msg}");
}

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

Execution result

normal message
[WARN] This is a warning

Note: When adding a new method to an existing interface, providing a default implementation will not break the existing implementation class.

Example 4 — ExplicitImpl: Explicit interface implementation

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

var p = new MyPrinter();

//p.Print();   // Compilation error — Since it is an explicit implementation, it is not directly visible in the class.

IPrintable pr = p;
pr.Print();    //Cast to interface and call

((IPrintable)p).Print();   //same meaning
// 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 name." Attach and do not use access modifiers.
    void IPrintable.Print() => Console.WriteLine("(Can only be called through the interface) Outputting");
}

Execution result

(Can only be called through the interface) Outputting
(Can only be called through the interface) Outputting

Note: Explicit implementation is useful when you need to implement two interfaces with the same name method, or when you do not want to expose the method to the outside world.

Common Mistakes

  1. Attach public to the interface member — All members are public by default, and if specified, a compilation error occurs (C# 8+ except for some exceptions).
  2. When implementing in a class, omit public — public must be specified on the class side (explicit implementation is an exception).
  3. We want to put a field in the interface — no field in the interface, OK property.
  4. Trying to call it directly as a class instance after explicit implementation → Compilation error. Requires casting to interface variable.
  5. I don't know that interfaces can be inherited like interface IFoo : IBar (extended interface pattern).

Cleanup

  • An interface is a contract of “ability to do”. There is no member body and no fields.
  • A class can implement multiple interfaces simultaneously — a safe replacement for class multiple inheritance.
  • Polymorphism works naturally with interface variables.
  • The default implementation (C# 8+) and explicit implementation are solutions to conflict/extension scenarios.

Try it yourself

cd src/IPrintable
dotnet run

cd ../MultipleInterfaces
dotnet run

cd ../DefaultMember
dotnet run

cd ../ExplicitImpl
dotnet run

Example files

The runnable files used in this lesson. Copy them into your own project and experiment.

src/DefaultMember/ConsoleLogger.csC#
namespace CodingNow.Lecture.Oop10;

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

    //Warn is not implemented → The default implementation of the interface is used.
}
src/DefaultMember/DefaultMember.csprojText
<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.csC#
namespace CodingNow.Lecture.Oop10;

internal interface ILogger
{
    void Log(string msg);

    //Default implementation (default interface member, C# 8+):
    //If the implementation class does not define it separately, this behavior is used as is.
    void Warn(string msg) => Log($"[WARN] {msg}");
}
src/DefaultMember/Program.csC#
using CodingNow.Lecture.Oop10;

ILogger lg = new ConsoleLogger();
lg.Log("normal message");
lg.Warn("This is a warning");   //Since ConsoleLogger did not implement it, the default implementation of the interface is called.

//new ConsoleLogger().Warn("...");   // The default implementation can only be called with interface types
src/ExplicitImpl/ExplicitImpl.csprojText
<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.csC#
namespace CodingNow.Lecture.Oop10;

internal interface IPrintable
{
    void Print();
}
src/ExplicitImpl/MyPrinter.csC#
namespace CodingNow.Lecture.Oop10;

internal class MyPrinter : IPrintable
{
    //Explicit implementation: Add “interface.name” in front of the method name and do not use access modifiers.
    //This way, it will not be exposed as a regular method in the class.
    void IPrintable.Print() => Console.WriteLine("(Can only be called through the interface) Outputting");
}
src/ExplicitImpl/Program.csC#
using CodingNow.Lecture.Oop10;

var p = new MyPrinter();

//p.Print();   // Because it is an explicit implementation, it is not directly visible in the class (uncommenting it will result in a compilation error)

//1) Receive and call as an interface variable
IPrintable pr = p;
pr.Print();

//2) Immediately cast to an interface and call it
((IPrintable)p).Print();
src/IPrintable/Book.csC#
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/Invoice.csC#
namespace CodingNow.Lecture.Oop10;

internal class Invoice : IPrintable
{
    public int Amount;

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

    public void Print() => Console.WriteLine($"Bill: {Amount} Won");
}
src/IPrintable/IPrintable.csC#
namespace CodingNow.Lecture.Oop10;

//Convention: Interface names start with I.
internal interface IPrintable
{
    void Print();
}
src/IPrintable/IPrintable.csprojText
<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/Program.csC#
using CodingNow.Lecture.Oop10;

//Even if there is no parent-child relationship, if the same interface is implemented, they can be processed together.
IPrintable[] items = [new Book("Facts and misconceptions about object orientation"), new Invoice(99000)];

foreach (var item in items)
{
    item.Print();
}
src/MultipleInterfaces/Image.csC#
namespace CodingNow.Lecture.Oop10;

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

    public void Resize(int width, int height)
        => Console.WriteLine($"Change image size to {width}x{height}");
}
src/MultipleInterfaces/Interfaces.csC#
namespace CodingNow.Lecture.Oop10;

internal interface IPrintable
{
    void Print();
}

internal interface IResizable
{
    void Resize(int width, int height);
}
src/MultipleInterfaces/MultipleInterfaces.csprojText
<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.csC#
using CodingNow.Lecture.Oop10;

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

Assignment

Problem 1 — Sort by IComparable<T> implementation

  • Reference solution: shown below
  • Core concepts: Implementing standard library interfaces, utilizing sorting

Requirements

  • Create the Student class. The fields are Name(string), Score(int).
  • Implement IComparable<Student> and compare CompareTo in descending score order.
  • Put 3~4 students in List<Student> and print it out after calling Sort().

Expected output

Younghee: 95
Withdrawal: 80
Number of people: 70

Hint

  • int.CompareTo(other) returns a negative number if it is smaller, 0 if it is equal, and a positive number if it is larger.
  • To sort in descending order, reverse the comparison order like other.Score.CompareTo(this.Score).
  • using System; is automatically included as ImplicitUsings, so there is no need to add it separately.

Problem 2 — Implementing two interfaces IDrawable + IResizable

  • Reference solution: shown below
  • Key concepts: Implementing multiple interfaces, calling them as interface variables

Requirements

  • IDrawable interface: void Draw().
  • IResizable interface: void Resize(int width, int height).
  • The Rectangle class implements both interfaces. It has a Horizontal/Vertical field.
  • Draw() outputs current size information, and Resize outputs a message after changing the size.
  • Create a Rectangle object from Program.cs and call two methods.

Expected output

Draw a square (10x5)
Change rectangle size → (20x10)
Draw a square (20x10)

Hint

  • Listed with commas, like class Rectangle : IDrawable, IResizable.
  • Both interface methods must be implemented as public.

Check the correct answer

After solving it yourself, compare it with the reference solution below.

Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

Homework01/Homework01.csprojText
<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>
Homework01/Program.csC#
using CodingNow.Lecture.Oop10;

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

students.Sort();   //CompareTo of IComparable<Student> is called (scoring in descending order)

foreach (var s in students)
{
    Console.WriteLine($"{s.Name}: {s.Score}");
}
Homework01/Student.csC#
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;
    }

    //Sort by descending score: Reverse the comparison order of other and this.
    public int CompareTo(Student? other)
    {
        if (other is null) return 1;
        return other.Score.CompareTo(this.Score);
    }
}
Homework02/Homework02.csprojText
<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>
Homework02/Interfaces.csC#
namespace CodingNow.Lecture.Oop10;

internal interface IDrawable
{
    void Draw();
}

internal interface IResizable
{
    void Resize(int width, int height);
}
Homework02/Program.csC#
using CodingNow.Lecture.Oop10;

var rect = new Rectangle(10, 5);
rect.Draw();
rect.Resize(20, 10);
rect.Draw();
Homework02/Rectangle.csC#
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($"Draw a rectangle ({Width}x{Height})");

    public void Resize(int width, int height)
    {
        Width = width;
        Height = height;
        Console.WriteLine($"Change rectangle size → ({Width}x{Height})");
    }
}

Learn this lesson with a mentor

Work through the C# (.NET 8 LTS) track on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now