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:

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:
| Gateway | Fee rule | Refunds? | Fee on $84.50 |
|---|---|---|---|
| CardPay | 2.9% + $0.30 | Yes, keeps the $0.30 | $2.75 |
| BankLink | 0.8%, capped at $5.00 | Yes, in full | $0.68 |
| WalletGo | 3.49% + $0.49 | No | $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

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

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

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

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 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)

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.
| Feature | Interface | Abstract class |
|---|---|---|
| How many per class | Any number | One (single inheritance) |
| Instance fields / state | No | Yes |
| Constructors | No | Yes |
| Method bodies | Default methods (C# 8+) | Yes, any member |
| Default member access | public | private |
| Works with structs | Yes | No |
| Best for | A capability unrelated types share | A 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
- Implement
IDisposableon a gateway that holds an HTTP connection; my guide to Dispose and Finalize in C# shows the pattern. - Give a record
IComparable<T>so arrays can sort themselves in C#. - Add a
PaymentFailedevent to the interface, the same way events in C# work on classes.
Other C# type and OOP tutorials you may like:
- Delegates in C# and when they beat a one-method interface
- Structs in C#, which can implement interfaces too
- Method overloading in C# with real examples
- Partial classes in C# for splitting large types
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.
Bijay Kumar is a renowned software engineer, accomplished author, and distinguished Microsoft Most Valuable Professional (MVP) specializing in SharePoint. With a rich professional background spanning over 15 years, Bijay has established himself as an authority in the field of information technology. He possesses unparalleled expertise in multiple programming languages and technologies such as ASP.NET, ASP.NET MVC, C#.NET, and SharePoint, which has enabled him to develop innovative and cutting-edge solutions for clients across the globe. Read more…