Interfaces & Abstract Classes

C# · Lesson 19 of 31 6 min read Updated

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.

C#
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;
}
Output
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:

C#
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);
}
Output
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.

C#
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.";
}
Output
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.

C#
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;
}
Output
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?

InterfaceAbstract class
A type can havemanyonly one (single inheritance)
Fields and constructorsno instance fields, no constructorsyes
Shared codeonly default methodsyes, any normal members
Best for"can do" abilities shared by unrelated types"is a" families that share code and state
ExamplesIComparable<T>, IDisposable, IPaymentMethodRoom, 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 it public.
  • Trying to create an interface or abstract class with new. Create a concrete class that implements or derives from it.
  • Forgetting override on abstract members. An abstract member must be overridden with override, 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 : IName and 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
C#
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;
}
Output
Book { Title = Godan, WeightKg = 0.5 }: Rs 20.00
Laptop { Model = ThinkPad, WeightKg = 2 }: Rs 270.00
Total shipping: Rs 290.00

The 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?

2. Which of these can you NOT do with an abstract class?

3. What does IComparable<T>.CompareTo returning a negative number mean?

4. A class implements an interface but leaves out one of its methods. What happens?

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