Interfaces & Abstract Classes
Inheritance says what something is. Often you care more about what something can do: a payment method can calculate its fee, a document can be printed, a player can be compared with another player. An interface is a contract that lists the members a type promises to provide, without saying how. Any class, record or struct can sign that contract.
An abstract class sits halfway between an interface and a normal class: it can contain shared code, but it also leaves some members for derived classes to fill in. In this lesson you will write and use both, and learn when to pick which.
Defining and implementing an interface
An interface declares members without bodies. By convention its name starts with I. A class implements the interface by listing it after a colon and providing every member.
List<IPaymentMethod> methods = [new Upi(), new Card(1.5m), new Cash()];
decimal bill = 2000m;
foreach (IPaymentMethod m in methods)
{
decimal fee = m.Fee(bill);
Console.WriteLine($"{m.Name,-6} fee Rs {fee,6:F2} total Rs {bill + fee:F2}");
}
interface IPaymentMethod
{
string Name { get; }
decimal Fee(decimal amount);
}
class Upi : IPaymentMethod
{
public string Name => "UPI";
public decimal Fee(decimal amount) => 0m;
}
class Card(decimal feePercent) : IPaymentMethod
{
public string Name => "Card";
public decimal Fee(decimal amount) => amount * feePercent / 100;
}
class Cash : IPaymentMethod
{
public string Name => "Cash";
public decimal Fee(decimal amount) => 0m;
}UPI fee Rs 0.00 total Rs 2000.00 Card fee Rs 30.00 total Rs 2030.00 Cash fee Rs 0.00 total Rs 2000.00
The loop works with any IPaymentMethod. Upi, Card and Cash share no base class and no code; they only share the contract. Tomorrow you can add Wallet or NetBanking, and the billing code does not change. Interface members are public automatically, so the implementing members must be public too. If a class forgets a member, the compiler stops with error CS0535 ("does not implement interface member").
A class can inherit from only one base class, but it can implement many interfaces: class SmartCard : Card, IContactless, IRefundable. This is how C# shares abilities across unrelated types.
Interfaces from .NET
.NET defines many interfaces that unlock built-in features when you implement them. IComparable<T> tells sorting methods how to order your objects:
List<Player> squad = [new("Rohan", 412), new("Kabir", 655), new("Farhan", 298)];
squad.Sort();
foreach (Player p in squad)
{
Console.WriteLine(p);
}
record Player(string Name, int Runs) : IComparable<Player>
{
// Higher runs first
public int CompareTo(Player? other) => other is null ? -1 : other.Runs.CompareTo(Runs);
}Player { Name = Kabir, Runs = 655 }
Player { Name = Rohan, Runs = 412 }
Player { Name = Farhan, Runs = 298 }CompareTo returns a negative number if this object should come first, zero if they are equal, and a positive number if it should come after. Comparing other.Runs with Runs (rather than the other way round) gives descending order. Other common .NET interfaces are IEnumerable<T> (anything you can foreach over), IEquatable<T> (custom equality) and IDisposable (objects that must release a file or connection, used with using, which you will see in File I/O & JSON).
Default interface methods
Since C# 8, an interface can also provide a default implementation for a member. Implementing types get it for free and may replace it.
IGreeter guest = new Guest("Meera");
IGreeter vip = new VipGuest("Farhan");
Console.WriteLine(guest.Greet());
Console.WriteLine(vip.Greet());
interface IGreeter
{
string Name { get; }
string Greet() => $"Namaste, {Name}!";
}
class Guest(string name) : IGreeter
{
public string Name => name;
}
class VipGuest(string name) : IGreeter
{
public string Name => name;
public string Greet() => $"Welcome back, {Name}. Your lounge is ready.";
}Namaste, Meera! Welcome back, Farhan. Your lounge is ready.
Default methods are mainly for library authors who need to add a member to an interface without breaking every class that already implements it. Note that Guest does not get a public Greet method of its own: new Guest("Meera").Greet() does not compile. You call a default method through the interface type, as above.
Abstract classes
An abstract class cannot be created with new. It exists to be a base class. It can have normal members with code, plus abstract members with no body that every derived class must override.
List<Room> flat = [new RectRoom("Bedroom", 12, 10), new LRoom("Hall", 12, 10, 6, 4)];
foreach (Room r in flat)
{
Console.WriteLine($"{r.Name}: {r.Area()} sq ft, flooring Rs {r.FlooringCost(85m)}");
}
abstract class Room(string name)
{
public string Name { get; } = name;
public abstract int Area();
public decimal FlooringCost(decimal ratePerSqFt) => Area() * ratePerSqFt;
}
class RectRoom(string name, int length, int width) : Room(name)
{
public override int Area() => length * width;
}
class LRoom(string name, int l1, int w1, int l2, int w2) : Room(name)
{
public override int Area() => l1 * w1 + l2 * w2;
}Bedroom: 120 sq ft, flooring Rs 10200 Hall: 144 sq ft, flooring Rs 12240
FlooringCost is written once in Room and works for every shape, because it calls the abstract Area(), which each derived class must supply. Writing new Room("Store") fails with CS0144: Cannot create an instance of the abstract type or interface 'Room'.
Interface or abstract class?
| Interface | Abstract class | |
|---|---|---|
| A type can have | many | only one (single inheritance) |
| Fields and constructors | no instance fields, no constructors | yes |
| Shared code | only default methods | yes, any normal members |
| Best for | "can do" abilities shared by unrelated types | "is a" families that share code and state |
| Examples | IComparable<T>, IDisposable, IPaymentMethod | Room, Stream in .NET |
A good rule: start with an interface. Add an abstract base class only when several implementations really share code or fields. Many designs use both: an interface for the contract, and an abstract class that implements it partly to help common cases.
Common mistakes
- Missing an interface member. Error
CS0535. Implement every member, and make itpublic. - Trying to create an interface or abstract class with
new. Create a concrete class that implements or derives from it. - Forgetting
overrideon abstract members. An abstract member must be overridden withoverride, or the derived class must also be abstract. - Huge interfaces. An interface with twenty members is hard to implement. Split it into small, focused ones.
- Calling a default interface method on the class variable. Use a variable of the interface type.
Key points
- An interface is a contract of members; a type implements it with
: INameand supplies every member. - A class can implement many interfaces but inherit only one class.
- Code written against an interface works with any type that implements it.
- .NET interfaces such as
IComparable<T>plug your types into built-in features like sorting. - Default interface methods (C# 8) provide optional shared behaviour.
- An abstract class cannot be instantiated; it mixes shared code with abstract members that derived classes override.
Exercise
Define an interface IShippable with a property WeightKg (decimal) and a method ShippingCost(). Create two records that implement it: Book(string Title, decimal WeightKg), charged Rs 40 per kg, and Laptop(string Model, decimal WeightKg), charged Rs 60 per kg plus Rs 150 insurance. Put a book of 0.5 kg and a laptop of 2 kg in a List<IShippable> and print each item and the total shipping cost.
Show solution
List<IShippable> cart = [new Book("Godan", 0.5m), new Laptop("ThinkPad", 2m)];
decimal total = 0m;
foreach (IShippable item in cart)
{
Console.WriteLine($"{item}: Rs {item.ShippingCost():F2}");
total += item.ShippingCost();
}
Console.WriteLine($"Total shipping: Rs {total:F2}");
interface IShippable
{
decimal WeightKg { get; }
decimal ShippingCost();
}
record Book(string Title, decimal WeightKg) : IShippable
{
public decimal ShippingCost() => WeightKg * 40m;
}
record Laptop(string Model, decimal WeightKg) : IShippable
{
public decimal ShippingCost() => WeightKg * 60m + 150m;
}Book { Title = Godan, WeightKg = 0.5 }: Rs 20.00
Laptop { Model = ThinkPad, WeightKg = 2 }: Rs 270.00
Total shipping: Rs 290.00The positional property WeightKg satisfies the interface's WeightKg property automatically. The cart loop only knows about IShippable, so new product types can be added without changing it.
Quick Quiz
1. How many interfaces can a C# class implement?
A class has one base class but can implement any number of interfaces.
2. Which of these can you NOT do with an abstract class?
Abstract classes cannot be instantiated; you create objects of derived classes.
3. What does IComparable<T>.CompareTo returning a negative number mean?
Negative means "before", zero means "same position", positive means "after".
4. A class implements an interface but leaves out one of its methods. What happens?
Every interface member without a default implementation must be implemented.
Learn C# with a mentor
Free lessons get you started. Our Programming & Software Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
