İçeriğe geç / Skip to content / Zum Inhalt
Ahmet Balaman LogoAhmet Balaman

C# Interface vs Abstract Class: Differences and When to Use

Ahmet Balaman

7 min read

.NETC#OOPInterfaceAbstract ClassExam Questions
C# Interface vs Abstract Class: Differences and When to Use

Interface and abstract class are the two C# concepts students mix up most. Both look like "a type you cannot instantiate that acts as a template for others", and when an exam asks for the difference, most answers turn into a memorised table. The difference fits in one sentence, though: an interface is a capability contract ("I can do this"), while an abstract class is an unfinished class ("this is what I am, but a derived class has to complete part of me"). This post walks through what each may contain, how to choose between them, and how the topic shows up in exams, using a payment system as the running example.

Interface: A Capability Contract

An interface states which members a type exposes. It does not care how they work. Think back to the e-commerce order from the constructor post. The order has to be paid, but the order class should not need to know whether that happens by card or by bank transfer:

public interface IPaymentMethod
{
    string Name { get; }
    bool Pay(decimal amount);
}

public interface IRefundable
{
    bool Refund(decimal amount);
}

Things to notice: the members have no body, there is no access modifier (interface members are public by default), and we cannot declare a field. An interface holds no state. It only promises that these methods and properties exist.

Abstract Class: An Unfinished Class

An abstract class is a real class: it can have fields, constructors and fully implemented methods. The only differences are that you cannot create it with new, and that it may mark some members abstract to tell derived classes "you write this part".

Payment methods share a flow: check the amount, add the fee, perform the payment, record the result. Instead of repeating that in every class, we write it once:

public abstract class PaymentBase : IPaymentMethod
{
    private readonly List<string> _log = new();

    protected PaymentBase(string name)
    {
        Name = name;
    }

    public string Name { get; }
    public IReadOnlyList<string> Log => _log;

    // Shared flow: every payment method goes through the same steps
    public bool Pay(decimal amount)
    {
        if (amount <= 0)
        {
            Record("Invalid amount rejected");
            return false;
        }

        decimal total = amount + CalculateFee(amount);
        bool result = ProcessPayment(total);
        Record($"{total:N2} -> {(result ? "succeeded" : "failed")}");
        return result;
    }

    // Derived classes MUST write this
    protected abstract bool ProcessPayment(decimal total);

    // Derived classes MAY change this
    protected virtual decimal CalculateFee(decimal amount) => 0m;

    protected void Record(string message) => _log.Add($"[{Name}] {message}");
}

There are three things here an interface cannot do: a private field called _log, a protected constructor, and ready-made code shared by all derived classes. If protected is new to you, read the access modifiers post; the details of abstract, virtual and override are covered in the inheritance and polymorphism guide.

What Can Each One Contain?

Feature interface abstract class
Instance fields No Yes
Constructor No Yes (derived classes call it via base(...))
Methods with a body Only as default implementations Yes
Members without a body Yes (the normal case) Yes (with abstract)
Access modifiers public by default All of them
How many can a class have? As many as it wants Exactly one
Usable by a struct? Yes No
Instantiable with new No No

Default Interface Members

"Interfaces have no bodies" has not been strictly true since C# 8. You can give an interface member a default implementation:

public interface IPaymentMethod
{
    string Name { get; }
    bool Pay(decimal amount);

    // Default implementation - C# 8 and later
    string Summary() => $"Payment by {Name}";
}

The feature exists mainly so you can add a member to a published interface without breaking every class that already implements it. It needs runtime support, so it is not available on the old .NET Framework; on modern .NET it just works. Two limits remain: an interface still cannot hold instance fields and still has no constructor. Default members do not turn an interface into an abstract class. Interfaces may also contain static members, but you will not need those at beginner level.

Single Inheritance, Multiple Interfaces

A C# class can have only one base class, but it can implement any number of interfaces. The credit card inherits the shared payment flow and adds the refund capability on top:

public class CreditCard : PaymentBase, IRefundable
{
    private readonly string _cardNumber;

    public CreditCard(string cardNumber) : base("Credit Card")
    {
        _cardNumber = cardNumber;
    }

    protected override bool ProcessPayment(decimal total)
    {
        // A real project would call the bank's service here
        return _cardNumber.Length == 16;
    }

    protected override decimal CalculateFee(decimal amount) => amount * 0.02m;

    public bool Refund(decimal amount)
    {
        Record($"{amount:N2} refunded");
        return true;
    }
}

public class BankTransfer : PaymentBase
{
    private readonly string _iban;

    public BankTransfer(string iban) : base("Bank Transfer")
    {
        _iban = iban;
    }

    protected override bool ProcessPayment(decimal total) => _iban.StartsWith("TR");
}

One important detail: nobody is forced to use the base class. A payment method that does not fit the shared flow can implement the interface directly:

public class GiftCard : IPaymentMethod
{
    private decimal _remaining;

    public GiftCard(decimal amount)
    {
        _remaining = amount;
    }

    public string Name => "Gift Card";

    public bool Pay(decimal amount)
    {
        if (amount <= 0 || amount > _remaining) return false;
        _remaining -= amount;
        return true;
    }
}

Mini Scenario: A Checkout Screen

Picture a checkout screen: the user picks a payment method, the system attempts the payment and shows a message based on the result. The screen code has no need to know the concrete classes:

var cartTotal = 1250m;

List<IPaymentMethod> methods =
[
    new CreditCard("4111111111111111"),
    new BankTransfer("TR00 0000 0000 0000 0000 0000 00"),
    new GiftCard(500m)
];

foreach (var method in methods)
{
    bool success = method.Pay(cartTotal);
    Console.WriteLine($"{method.Summary()} -> {(success ? "approved" : "declined")}");

    if (success && method is IRefundable refundable)
        Console.WriteLine($"  Refund possible: {refundable.Refund(100m)}");
}

Output:

Payment by Credit Card -> approved
  Refund possible: True
Payment by Bank Transfer -> approved
Payment by Gift Card -> declined

The loop works with three different classes yet only knows IPaymentMethod. If a "Crypto" method arrives tomorrow, the loop stays untouched. Refunding is a separate interface, so we can ask for it with is: a gift card cannot be refunded, and the type system tells us so. The same idea is the foundation of dependency injection in ASP.NET Core; in the Minimal API getting started post we register and consume a service through an interface.

.NET itself is full of this pattern: Array.Sort and List<T>.Sort expect your type to implement IComparable<T> so elements can be compared. If you are curious how the sorting happens underneath, the sorting algorithms exam questions post is a good follow-up.

When to Use It and When Not To

Choose an interface when:

  • Unrelated types need to share a capability (like IComparable<T> or IDisposable).
  • A class has to play more than one role.
  • You want to swap the real class for a fake one in tests or through dependency injection.
  • struct types should be able to join the contract.

Choose an abstract class when:

  • Derived classes really are "a kind of X" and share fields or code.
  • A fixed flow, like the Pay method above, varies only in a few steps.
  • There is common data you want to enforce through the constructor.

Use both together: this is the layout you will meet most often in practice. The interface defines the contract, the abstract class takes over the repetitive part, and outside code only knows the interface.

Use neither: if there is a single implementation and no concrete reason to expect a second one, a plain class is enough. Reflexively creating an I... interface for every class makes code harder to read. And for a handful of fixed values, an enum or a record is the better tool.

Common Mistakes

1. Leaving out an interface member

Symptom: CS0535: 'CreditCard' does not implement interface member 'IPaymentMethod.Pay(decimal)'. Fix: write the member with the same signature and make it public. The abstract class counterpart is CS0534; there the fix is to override the member or to mark your class abstract as well.

2. Trying to instantiate an abstract class

var payment = new PaymentBase("Test");
// CS0144: Cannot create an instance of the abstract type or interface 'PaymentBase'

IPaymentMethod payment2 = new BankTransfer("TR00 ...");   // Correct: create a concrete class

3. Calling a default interface member through a class variable

Default members are not inherited by the class; they are only visible through the interface type:

var card = new CreditCard("4111111111111111");
// card.Summary();   // CS1061: 'CreditCard' does not contain a definition for 'Summary'

IPaymentMethod method = card;
Console.WriteLine(method.Summary());   // Payment by Credit Card

4. Listing the base class after the interfaces

Writing class CreditCard : IRefundable, PaymentBase gives you CS1722: Base class 'PaymentBase' must come before any interfaces. The order is always base class first, interfaces after.

Typical Exam Questions

Question 1: If an abstract class cannot be instantiated, what is its constructor for?

When an object of a derived class is created, the base class constructor runs first. That is how the abstract class initialises its own shared fields. The base("Credit Card") call in the example is exactly that.

Question 2: Can you write an abstract class with no abstract members at all?

Yes. The abstract keyword only means "do not instantiate this class directly". The reverse does not hold: a class containing an abstract member must itself be abstract.

Question 3: Can a class derive from two abstract classes?

No. C# has no multiple inheritance for classes; the compiler reports CS1721. Implementing several interfaces is fine.

Question 4: What happens when two interfaces declare a method with the same signature?

A single public method satisfies both. If they need to behave differently, use explicit implementation:

public interface IPrinter { void Start(); }
public interface IScanner { void Start(); }

public class MultiFunctionPrinter : IPrinter, IScanner
{
    void IPrinter.Start() => Console.WriteLine("Printing started");
    void IScanner.Start() => Console.WriteLine("Scanning started");
}

var device = new MultiFunctionPrinter();
((IPrinter)device).Start();   // Printing started
((IScanner)device).Start();   // Scanning started
// device.Start();            // Does not compile: explicit members are only visible via the interface

Question 5: Can an interface declare a field?

Not an instance field; the compiler reports CS0525. Properties are allowed because a property is really a pair of methods.

If you want to practise solving questions like these on paper, have a look at the exam support page.

Frequently Asked Questions

Which is faster, an interface or an abstract class?

In everyday application code the difference is not something you will measure as a problem. Decide by design, not performance: are you defining a capability or sharing code?

Can an abstract class implement an interface?

Yes, PaymentBase in the example does exactly that. It can even mark the interface members abstract and leave them to the derived classes instead of implementing them itself.

Do default interface members make abstract classes obsolete?

No. An interface still cannot contain instance fields or a constructor, so it cannot hold state. When you need shared data and shared initialisation logic, the abstract class keeps its place.

Why do interface names start with the letter I?

It is not a language rule but an established .NET naming convention. Anyone reading IPaymentMethod knows at once that it is a contract, so I recommend sticking to it.

Comments