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
interfacekeyword. - 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
- Attach
publicto the interface member — All members are public by default, and if specified, a compilation error occurs (C# 8+ except for some exceptions). - When implementing in a class, omit
public—publicmust be specified on the class side (explicit implementation is an exception). - We want to put a field in the interface — no field in the interface, OK property.
- Trying to call it directly as a class instance after explicit implementation → Compilation error. Requires casting to interface variable.
- 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