Interface in C# with Real-Time Example

Write your first interface in C#, skip one member in the class that implements it, and the build stops cold:

Console.WriteLine("Checkout ready");

interface IPaymentGateway { string Name { get; } decimal Fee(decimal amount); }

class WalletGo : IPaymentGateway
{
    public string Name => "WalletGo";
}
// error CS0535: 'WalletGo' does not implement interface member 'IPaymentGateway.Fee(decimal)'

An interface is a contract. Every class that lists it must implement every member, so adding the missing Fee method fixes CS0535.

We’ll build a three-gateway checkout on .NET 10, one interface skill per step. Here’s what we’re building:

Interface in C# example in VS Code: three payment gateway classes used through one IPaymentGateway list print CardPay $2.75, BankLink $0.68 and WalletGo $3.44 fees and pick BankLink as the cheapest
The finished checkout: three classes, one interface, a default Quote method and a refund check

Before You Start Building the C# Interface Example

You need the .NET 10 SDK and VS Code; every step runs with dotnet run File.cs. If classes and objects are still new, skim my OOP concepts in C# overview first.

The running sample is a Portland coffee roaster’s online checkout. A typical order is $84.50, and the shop can route it through three made-up gateways:

GatewayFee ruleRefunds?Fee on $84.50
CardPay2.9% + $0.30Yes, keeps the $0.30$2.75
BankLink0.8%, capped at $5.00Yes, in full$0.68
WalletGo3.49% + $0.49No$3.44

Step 1: Declare an Interface in C#

An interface lists the members a type must have, without saying how they work. Ours needs a Name property and a Fee method:

using System.Globalization;
CultureInfo.CurrentCulture = new("en-US");

IPaymentGateway gateway = new CardPay();
Console.WriteLine($"{gateway.Name} fee on $84.50: {gateway.Fee(84.50m):C}");

interface IPaymentGateway
{
    string Name { get; }              // read-only property
    decimal Fee(decimal amount);      // method signature, no body
}

class CardPay : IPaymentGateway
{
    public string Name => "CardPay";
    public decimal Fee(decimal amount) => amount * 0.029m + 0.30m;
}

Output:

CardPay fee on $84.50: $2.75
Declare an interface in C# in VS Code: IPaymentGateway with a Name property and a Fee method, implemented by CardPay, prints CardPay fee on $84.50: $2.75
The interface holds signatures; CardPay supplies the bodies

Members without a body are implicitly public, so the class must implement them as public. The I prefix is the .NET habit covered in my C# naming conventions guide.

Notice the variable type: IPaymentGateway gateway. You can’t write new IPaymentGateway(), but an interface works fine as a variable, parameter or list type.

Checkpoint: CardPay fee on $84.50: $2.75.

Step 2: Implement the Interface in Three Classes

Now add BankLink and WalletGo. Each class does its own math, and one List<IPaymentGateway> holds all three:

using System.Globalization;
CultureInfo.CurrentCulture = new("en-US");

List<IPaymentGateway> gateways = [new CardPay(), new BankLink(), new WalletGo()];
foreach (var g in gateways)
    Console.WriteLine($"{g.Name,-9}{g.Fee(84.50m),6:C}");

interface IPaymentGateway { string Name { get; } decimal Fee(decimal amount); }

class CardPay : IPaymentGateway {
    public string Name => "CardPay";
    public decimal Fee(decimal a) => a * 0.029m + 0.30m;
}
class BankLink : IPaymentGateway {
    public string Name => "BankLink";
    public decimal Fee(decimal a) => Math.Min(a * 0.008m, 5.00m);
}
class WalletGo : IPaymentGateway {
    public string Name => "WalletGo";
    public decimal Fee(decimal a) => a * 0.0349m + 0.49m;
}

Output:

CardPay   $2.75
BankLink  $0.68
WalletGo  $3.44
C# interface implemented by three classes in VS Code: a List of IPaymentGateway loops over CardPay, BankLink and WalletGo and prints $2.75, $0.68 and $3.44
Three classes, one list type: that’s polymorphism through an interface

The foreach loop never asks which class it’s holding; it just calls Fee. That’s polymorphism without a shared base class. If the [...] collection syntax is new, my List in C# tutorial explains it.

Checkpoint: BankLink is the cheapest at $0.68 because 0.8% of $84.50 is only $0.676.

Step 3: Use the Interface as a Constructor Parameter

Here’s where an interface earns its keep: your Checkout class takes any IPaymentGateway and never names a concrete class:

using System.Globalization;
CultureInfo.CurrentCulture = new("en-US");

var checkout = new Checkout(new BankLink());   // swap in any gateway
Console.WriteLine(checkout.Pay(84.50m));
Console.WriteLine(checkout.Pay(1250.00m));
Console.WriteLine(new Checkout(new CardPay()).Pay(1250.00m));

class Checkout(IPaymentGateway gateway)        // knows the interface only
{
    public string Pay(decimal amount) =>
        $"{amount:C} via {gateway.Name}, fee {gateway.Fee(amount):C}";
}

interface IPaymentGateway { string Name { get; } decimal Fee(decimal amount); }

class CardPay : IPaymentGateway {
    public string Name => "CardPay";
    public decimal Fee(decimal a) => a * 0.029m + 0.30m;
}
class BankLink : IPaymentGateway {
    public string Name => "BankLink";
    public decimal Fee(decimal a) => Math.Min(a * 0.008m, 5.00m);
}

Output:

$84.50 via BankLink, fee $0.68
$1,250.00 via BankLink, fee $5.00
$1,250.00 via CardPay, fee $36.55
C# interface as a constructor parameter in VS Code: a Checkout class receives BankLink or CardPay through IPaymentGateway and prints the fee for $84.50 and $1,250.00
Swap the gateway without touching Checkout

BankLink’s cap kicks in at $1,250.00: $5.00 instead of $36.55 with CardPay. Checkout uses a primary constructor (C# 12); my constructors in C# guide covers the syntax.

This is dependency injection in miniature. ASP.NET Core does the same with builder.Services.AddScoped<IPaymentGateway, BankLink>(), and tests pass a fake gateway. Hiding the fee details behind a contract is abstraction in C# in practice.

Checkpoint: $1,250.00 via BankLink, fee $5.00.

Step 4: Implement Multiple Interfaces in One Class

Only some gateways support refunds, so refunds get their own small interface. You can list as many interfaces as you need, separated by commas:

using System.Globalization;
CultureInfo.CurrentCulture = new("en-US");

List<IPaymentGateway> gateways = [new CardPay(), new WalletGo()];
foreach (var g in gateways)
{
    if (g is IRefundable r)
        Console.WriteLine($"{g.Name}: refund {r.Refund(84.50m):C}");
    else
        Console.WriteLine($"{g.Name}: no refunds");
}

interface IPaymentGateway { string Name { get; } decimal Fee(decimal amount); }
interface IRefundable { decimal Refund(decimal amount); }

class CardPay : IPaymentGateway, IRefundable {
    public string Name => "CardPay";
    public decimal Fee(decimal a) => a * 0.029m + 0.30m;
    public decimal Refund(decimal a) => a - 0.30m;   // keeps the fixed fee
}
class WalletGo : IPaymentGateway {
    public string Name => "WalletGo";
    public decimal Fee(decimal a) => a * 0.0349m + 0.49m;
}

Output:

CardPay: refund $84.20
WalletGo: no refunds
C# class implementing multiple interfaces in VS Code: CardPay implements IPaymentGateway and IRefundable, and the is IRefundable check prints a $84.20 refund for CardPay and no refunds for WalletGo
The is pattern asks whether an object also implements IRefundable

g is IRefundable r tests the object and hands you a typed variable in one go. Small, focused interfaces beat one giant one: WalletGo isn’t forced to fake a refund method.

This is how C# gets the benefits of multiple inheritance in C#: one base class at most, but any number of interfaces. If a class has a base class too, the base class comes first in the list.

Checkpoint: CardPay: refund $84.20 and WalletGo: no refunds.

Step 5: Use Explicit Interface Implementation for a Name Clash

Say CardPay prints a receipt line and writes an audit log, and both interfaces name their method Describe(). Prefix each method with its interface name:

var card = new CardPay();
Console.WriteLine(((IReceiptLine)card).Describe());

IAuditEntry audit = card;
Console.WriteLine(audit.Describe());

interface IReceiptLine { string Describe(); }
interface IAuditEntry { string Describe(); }

class CardPay : IReceiptLine, IAuditEntry
{
    string IReceiptLine.Describe() => "Paid $84.50 by Visa ending 1881";
    string IAuditEntry.Describe() => "2026-10-31 09:42 CardPay charge A1043 OK";
}

Output:

Paid $84.50 by Visa ending 1881
2026-10-31 09:42 CardPay charge A1043 OK
Explicit interface implementation in C# in VS Code: CardPay implements IReceiptLine.Describe and IAuditEntry.Describe separately, and each cast prints a different line
Same method name, two different results, picked by the interface you call through

Explicit members have no access modifier, and you can’t call them on the class: card.Describe() won’t compile. Cast to the interface or assign to an interface variable, as the code does.

Checkpoint: two different lines, the receipt first and 2026-10-31 09:42 CardPay charge A1043 OK second.

Step 6: Add a Default Interface Method (C# 8+)

Remember the belief that interfaces can’t hold code? Since C# 8 they can. Give Quote a body and existing classes keep compiling, with no changes:

using System.Globalization;
CultureInfo.CurrentCulture = new("en-US");

IPaymentGateway card = new CardPay(), bank = new BankLink();
Console.WriteLine(card.Quote(84.50m));   // default body from the interface
Console.WriteLine(bank.Quote(84.50m));   // BankLink's own version

interface IPaymentGateway
{
    string Name { get; }
    decimal Fee(decimal amount);
    string Quote(decimal amount) => $"{Name}: {Fee(amount):C} fee on {amount:C}";
}
class CardPay : IPaymentGateway {
    public string Name => "CardPay";
    public decimal Fee(decimal a) => a * 0.029m + 0.30m;
}
class BankLink : IPaymentGateway {
    public string Name => "BankLink";
    public decimal Fee(decimal a) => Math.Min(a * 0.008m, 5.00m);
    public string Quote(decimal a) => $"BankLink: {Fee(a):C} (capped at $5.00)";
}

Output:

CardPay: $2.75 fee on $84.50
BankLink: $0.68 (capped at $5.00)
Default interface method in C# 8 and later in VS Code: IPaymentGateway.Quote has a body that CardPay uses, while BankLink provides its own Quote with the $5.00 cap note
CardPay inherits the default Quote; BankLink replaces it

That’s the point of default methods: you can add a member to a published interface without breaking every implementer. A class that wants different behavior simply declares the member, much like method overriding in C#, minus the override keyword.

Important: a default method is only reachable through the interface type. That’s why card is declared as IPaymentGateway, not var.

Checkpoint: CardPay: $2.75 fee on $84.50 and BankLink: $0.68 (capped at $5.00).

Test the Finished C# Interface Checkout

Here’s the program from the top, with every step in it:

using System.Globalization;
CultureInfo.CurrentCulture = new("en-US");

List<IPaymentGateway> gateways = [new CardPay(), new BankLink(), new WalletGo()];
foreach (var g in gateways)
    Console.WriteLine(g.Quote(84.50m) + (g is IRefundable ? "  refunds: yes" : "  refunds: no"));

var best = gateways.MinBy(g => g.Fee(84.50m))!;
Console.WriteLine($"Cheapest for $84.50: {best.Name}");

interface IPaymentGateway
{
    string Name { get; }
    decimal Fee(decimal amount);
    string Quote(decimal amount) => $"{Name,-9}{Fee(amount),6:C}";
}
interface IRefundable { decimal Refund(decimal amount); }

class CardPay : IPaymentGateway, IRefundable {
    public string Name => "CardPay";
    public decimal Fee(decimal a) => a * 0.029m + 0.30m;
    public decimal Refund(decimal a) => a - 0.30m;
}
class BankLink : IPaymentGateway, IRefundable {
    public string Name => "BankLink";
    public decimal Fee(decimal a) => Math.Min(a * 0.008m, 5.00m);
    public decimal Refund(decimal a) => a;
}
class WalletGo : IPaymentGateway {
    public string Name => "WalletGo";
    public decimal Fee(decimal a) => a * 0.0349m + 0.49m;
}

Output:

CardPay   $2.75  refunds: yes
BankLink  $0.68  refunds: yes
WalletGo  $3.44  refunds: no
Cheapest for $84.50: BankLink

BankLink wins at $0.68 and also refunds in full, while WalletGo costs $3.44 and can’t refund. Each class keeps its fee math private behind the contract, which is encapsulation in C# working with interfaces.

Static Abstract Interface Members (C# 11)

Instance members need an object. When you want a type itself to provide a value, C# 11 (.NET 7+) adds static abstract members, used through a generic constraint:

Console.WriteLine(Settle<CardPay>());

static string Settle<T>() where T : IGatewayInfo =>
    $"{T.DisplayName} pays out in {T.SettlementDays} business days";

interface IGatewayInfo
{
    static abstract string DisplayName { get; }
    static abstract int SettlementDays { get; }
}
class CardPay : IGatewayInfo
{
    public static string DisplayName => "CardPay";
    public static int SettlementDays => 2;
}

Output:

CardPay pays out in 2 business days

The compiler resolves T.DisplayName at compile time. You’ll mostly meet this in generic math (INumber<T>) rather than everyday business code.

Interface vs Abstract Class in C#: Which One?

Both define a contract. You’ll find the full build in my abstract class in C# tutorial.

FeatureInterfaceAbstract class
How many per classAny numberOne (single inheritance)
Instance fields / stateNoYes
ConstructorsNoYes
Method bodiesDefault methods (C# 8+)Yes, any member
Default member accesspublicprivate
Works with structsYesNo
Best forA capability unrelated types shareA family of related classes sharing code

I reach for an interface first and add an abstract class only when implementers truly share state; see inheritance in C#.

Troubleshooting C# Interface Errors

CS0535 (does not implement interface member): the class is missing a member, or its signature differs. Match the name, return type and parameters exactly, as in the intro fix.

CS1061 on a default method: you called it on a class-typed variable. Here’s the error I got with var:

var card = new CardPay();
Console.WriteLine(card.Quote(84.50m));   // class variable, not the interface

interface IPaymentGateway
{
    decimal Fee(decimal amount);
    string Quote(decimal amount) => $"Fee: {Fee(amount)}";
}
class CardPay : IPaymentGateway { public decimal Fee(decimal a) => a * 0.029m + 0.30m; }
// error CS1061: 'CardPay' does not contain a definition for 'Quote' and no accessible extension method 'Quote' accepting a first argument of type 'CardPay' could be found (are you missing a using directive or an assembly reference?)

Declare the variable as IPaymentGateway, or cast to it. Note: an explicit implementation fails the same way when called on the class.

Where to Take It Next

Other C# type and OOP tutorials you may like:

When your classes and interfaces are in place, it pays to check them with automated tests. In unit testing in C# with xUnit, I write my first tests, run them from the command line and go through the Assert methods you’ll use most.

Frequently Asked Questions

What is an interface in C# in simple words?

It’s a contract: a named list of methods, properties, events or indexers. Any class, struct or record that implements it must provide every member that has no default body.

Can an interface contain code in C#?

Yes, since C# 8. Interfaces can have default method bodies, static members and constants, but still no instance fields. Default methods are called through the interface type.

Can a class implement multiple interfaces in C#?

Yes. List them after the colon, separated by commas, with any base class first. A class can inherit only one class but implement any number of interfaces.

Can we create an object of an interface in C#?

No. new IPaymentGateway() doesn’t compile. You create a class that implements it and store that object in an interface-typed variable.

What is explicit interface implementation in C#?

Prefixing a member with the interface name, like IAuditEntry.Describe(). It resolves name clashes, and the member is reachable only through the interface. Microsoft’s explicit implementation guide has more examples.

Are interface members public by default in C#?

Members without a body are implicitly public and can’t take another access modifier. See the interface keyword reference on Microsoft Learn.