Abstract Classes and Interfaces
Abstract Classes and Abstract Methods
Abstract classes are declared with the abstract modifier and cannot be instantiated. They can contain both abstract methods and concrete methods. Abstract methods have no method body and must be overridden in derived classes.
Example
abstract class Shape
{
public string Name { get; set; }
public abstract double Area();
public void Print()
{
Console.WriteLine($"{Name} Area: {Area():F2}");
}
}
class Circle : Shape
{
public double Radius { get; set; }
public Circle(double radius)
{
Name = "Circle";
Radius = radius;
}
public override double Area()
{
return Math.PI * Radius * Radius;
}
}
class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public Rectangle(double w, double h)
{
Name = "Rectangle";
Width = w;
Height = h;
}
public override double Area()
{
return Width * Height;
}
}
Circle c = new Circle(3);
Rectangle r = new Rectangle(4, 5);
c.Print();
r.Print();
Circle Area: 28.27
Rectangle Area: 20.00
💡 An abstract class is like a "half-finished template" — it specifies what subclasses must complete (abstract methods) and also provides parts that can be used directly (concrete methods).
Abstract Classes vs Regular Classes
| Feature | Regular Class | Abstract Class |
|---|---|---|
| Instantiation | Yes | No |
| Abstract methods | Cannot contain | Can contain |
| Concrete methods | Yes | Yes |
| Constructor | Yes | Yes |
| Fields | Yes | Yes |
| When inherited | Optionally override virtual | Must override abstract |
| sealed modifier | Yes | No (contradictory) |
Example
abstract class Logger
{
protected string prefix;
public Logger(string prefix)
{
this.prefix = prefix;
}
public abstract void Log(string message);
public void LogWithTimestamp(string message)
{
string ts = DateTime.Now.ToString("HH:mm:ss");
Log($"[{ts}] {message}");
}
}
class FileLogger : Logger
{
public FileLogger() : base("FILE") { }
public override void Log(string message)
{
Console.WriteLine($"{prefix}: {message}");
}
}
FileLogger fl = new FileLogger();
fl.Log("Service started");
fl.LogWithTimestamp("Database connected");
FILE: Service started
FILE: [14:30:00] Database connected
⚠️ Abstract classes can have constructors, but constructors cannot be abstract. Constructors are called via base() when a subclass is instantiated.
Interface Definition and Implementation
Interfaces are defined using the interface keyword and represent a contract of members. Classes that implement an interface must provide implementations for all its members. Interface members are implicitly public and cannot have access modifiers.
Example
interface IMovable
{
void Move(double dx, double dy);
double Speed { get; set; }
}
interface IDrawable
{
void Draw();
}
class Player : IMovable, IDrawable
{
public double X { get; set; }
public double Y { get; set; }
public double Speed { get; set; }
public Player(double x, double y, double speed)
{
X = x;
Y = y;
Speed = speed;
}
public void Move(double dx, double dy)
{
X += dx * Speed;
Y += dy * Speed;
Console.WriteLine($"Moved to ({X:F1}, {Y:F1})");
}
public void Draw()
{
Console.WriteLine($"Drawing player @ ({X:F1}, {Y:F1})");
}
}
Player p = new Player(0, 0, 2.0);
p.Draw();
p.Move(3, 4);
p.Draw();
Drawing player @ (0.0, 0.0)
Moved to (6.0, 8.0)
Drawing player @ (6.0, 8.0)
📌 Interface names should start with a capital I, such as IComparable, IDisposable — this is a C# naming convention.
Multiple Interface Implementation
A class can implement multiple interfaces, separated by commas. This is one way C# achieves polymorphism, compensating for the single-inheritance limitation.
Example
interface IWalkable
{
void Walk();
}
interface ISwimmable
{
void Swim();
}
interface IFlyable
{
void Fly();
}
class Duck : IWalkable, ISwimmable, IFlyable
{
public string Name { get; set; }
public Duck(string name)
{
Name = name;
}
public void Walk()
{
Console.WriteLine($"{Name} waddles along");
}
public void Swim()
{
Console.WriteLine($"{Name} swims in the water");
}
public void Fly()
{
Console.WriteLine($"{Name} flaps its wings and takes off");
}
}
Duck d = new Duck("Donald");
d.Walk();
d.Swim();
d.Fly();
IWalkable walker = d;
walker.Walk();
ISwimmable swimmer = d;
swimmer.Swim();
Donald waddles along
Donald swims in the water
Donald flaps its wings and takes off
Donald waddles along
Donald swims in the water
💡 Variables of different interface types can only call members defined by that interface — this reflects the Interface Segregation Principle.
Interface Default Methods (C# 8)
Starting with C# 8, interfaces can contain methods with default implementations. This allows adding new members to an interface without breaking existing implementing classes.
Example
interface ILogger
{
void Log(string message);
void LogWarning(string message)
{
Log($"[WARN] {message}");
}
void LogError(string message)
{
Log($"[ERROR] {message}");
}
}
class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
class SimpleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($">> {message}");
}
}
ConsoleLogger cl = new ConsoleLogger();
cl.Log("System started");
cl.LogWarning("Low memory");
cl.LogError("Connection failed");
SimpleLogger sl = new SimpleLogger();
sl.Log("System started");
sl.LogWarning("Low memory");
System started
[WARN] Low memory
[ERROR] Connection failed
>> System started
>> [WARN] Low memory
⚠️ Default methods are only available when called through the interface type. They cannot be called through a variable of the implementing class type (unless the implementing class also re-implements the method).
Abstract Class vs Interface Selection Guide
| Comparison | Abstract Class | Interface |
|---|---|---|
| Inheritance/Implementation | Single inheritance | Multiple implementation |
| Member types | Any | Methods, properties, events, indexers |
| Fields | Yes | No (before C# 8) |
| Constructor | Yes | No |
| Access modifiers | Any | Default public |
| Default implementation | Yes | Since C# 8 |
| Version compatibility | Adding members is safe | Adding members breaks implementations (before C# 8) |
| Design intent | "What it is" (IS-A) | "What it can do" (CAN-DO) |
Selection guidelines:
- Represents a hierarchical "is-a" relationship → Abstract class
- Represents cross-type capability or behavior → Interface
- Need to share code and state → Abstract class
- Need multiple inheritance capability → Interface
- Need to add new methods to an existing interface without breaking old code → Interface default method
🔥 Quick rule: Use abstract classes for shared identity, use interfaces for shared capability.
Explicit Interface Implementation
When multiple interfaces have members with the same name, or when you need to hide interface members, use explicit interface implementation. The syntax is return_type InterfaceName.MemberName. Explicitly implemented members cannot have access modifiers and can only be called through the interface type.
Example
interface IEnglish
{
void Speak();
}
interface IFrench
{
void Speak();
}
class BilingualPerson : IEnglish, IFrench
{
void IEnglish.Speak()
{
Console.WriteLine("Hello!");
}
void IFrench.Speak()
{
Console.WriteLine("Bonjour!");
}
public void Speak()
{
Console.WriteLine("Hi!");
}
}
BilingualPerson p = new BilingualPerson();
p.Speak();
IEnglish eng = p;
eng.Speak();
IFrench fr = p;
fr.Speak();
Hi!
Hello!
Bonjour!
📌 Explicitly implemented interface members are not visible on the class instance — you must cast to the corresponding interface type to call them. This is both a limitation and an encapsulation mechanism.
IComparable<T> in Practice
IComparable<T> is one of the most commonly used interfaces in .NET, used to define the natural sort order of a type. Both Array.Sort and List<T>.Sort depend on this interface.
Example
class Student : IComparable<Student>
{
public string Name { get; set; }
public int Score { get; set; }
public Student(string name, int score)
{
Name = name;
Score = score;
}
public int CompareTo(Student other)
{
if (other == null) return 1;
return Score.CompareTo(other.Score);
}
public override string ToString()
{
return $"{Name}({Score})";
}
}
Student[] students = new Student[]
{
new Student("Alice", 85),
new Student("Bob", 92),
new Student("Charlie", 78),
new Student("Diana", 95),
new Student("Eve", 88)
};
Console.WriteLine("Before sorting:");
foreach (var s in students)
Console.WriteLine(s);
Array.Sort(students);
Console.WriteLine("After sorting (by score ascending):");
foreach (var s in students)
Console.WriteLine(s);
Before sorting:
Alice(85)
Bob(92)
Charlie(78)
Diana(95)
Eve(88)
After sorting (by score ascending):
Charlie(78)
Alice(85)
Eve(88)
Bob(92)
Diana(95)
💻 CompareTo return value convention: negative means the current object comes before the other, zero means equal, positive means the current object comes after. With IComparable<T>, custom types can directly use Array.Sort, List<T>.Sort, and other sorting features.
❓ FAQ
📖 Summary
abstractclasses cannot be instantiated and can contain both abstract and concrete methods- Abstract methods have no body; derived classes must override them with
override - Interfaces define contracts; implementing classes must provide implementations for all members
- A class can implement multiple interfaces, compensating for the single-inheritance limitation
- C# 8 interfaces support default methods for backward compatibility
- Abstract classes represent "what it is"; interfaces represent "what it can do"
- Explicit interface implementation resolves naming conflicts and is only callable through the interface type
IComparable<T>defines natural ordering and works withSortmethods
📝 Exercises
- Define an abstract class
Vehiclewith an abstract methodStart()and a concrete methodStop(), then createCarandBikesubclasses to implement it - Define an interface
ISerializablewith methodstring Serialize(), and an interfaceIDeserializablewith methodvoid Deserialize(string data), then create a class that implements both interfaces - Define two interfaces
IPlayableandIRecordable, both with avoid Pause()method, and use explicit interface implementation to distinguish between them - Create a
Productclass that implementsIComparable<Product>to sort by price, and test withArray.Sort - Add a
void PrintHeader(string title)method with a default implementation to an existingIPrintableinterface, and verify that old implementing classes are not affected



