Interfaces
An interface lists members a type promises to provide, with no bodies, so one variable can hold any type that keeps the promise.
interface IName { ReturnType MemberName(parameters); PropertyType PropertyName { get; } }An interface is a list of members that a type promises to provide. A member here is a method or a property. The interface gives each member a name, a parameter list and a return type. It gives no body. The body is the code that actually runs, and the interface leaves that to whoever implements it.
An interface is a job description, not the person doing the job. It says what must be done and nothing about how.
How it works
You declare one with the interface keyword. By convention the name starts with a capital I. A class says it implements the interface by naming it after a colon. The compiler then refuses to build that class until every member is present and public. Interface members are public by default, so you do not write public inside the interface.
The payoff is the variable. A variable whose type is the interface can hold an object of any class that implements it. Code written against IPayable keeps working for the class you add next year.
Shorthand and longhand
A one-expression member can be written two ways. They compile to the same thing, so pick one:
public string Describe() => "invoice";
public string Describe()
{
return "invoice";
}What trips people up
You cannot write new IPayable(). There is no body to run, so you always create a concrete class and store it in the interface variable. An interface also cannot hold an instance field, because it holds no data of its own.
Example
// A variable of the interface type can hold any class that implements it.
IPayable[] items =
{
new Invoice("INV-1", 120m),
new Subscription(9m)
};
foreach (IPayable item in items)
{
// Only the members IPayable declares are visible through this variable.
Console.WriteLine($"{item.Describe()} owes {item.AmountDue}");
}
interface IPayable
{
decimal AmountDue { get; }
string Describe();
}
class Invoice : IPayable
{
public Invoice(string number, decimal amountDue)
{
Number = number;
AmountDue = amountDue;
}
public string Number { get; }
public decimal AmountDue { get; }
public string Describe() => $"invoice {Number}";
}
class Subscription : IPayable
{
// An expression-bodied constructor: the same as a body that assigns once.
public Subscription(decimal monthlyPrice) => AmountDue = monthlyPrice;
public decimal AmountDue { get; }
public string Describe() => "monthly subscription";
}
// Output:
// invoice INV-1 owes 120
// monthly subscription owes 9When to use it
- Write one reporting method that accepts any payable item instead of overloading it per class.
- Swap a real email sender for a recording one in tests without touching the code that sends.
- Hold mixed invoice and subscription objects in a single array and total them in one loop.
More examples
An interface as a parameter type
This is the whole point of interfaces in day-to-day code. Receipt takes IPayable, so it compiles against the promise and runs against any class that keeps it. You reach for this whenever a function should not care which concrete class it was handed.
// The method names the promise it needs, not the class it was written against.
static string Receipt(IPayable payable)
{
return $"{payable.Describe()}: {payable.AmountDue}";
}
Console.WriteLine(Receipt(new Invoice("INV-7", 50m)));
Console.WriteLine(Receipt(new Subscription(9m)));
interface IPayable
{
decimal AmountDue { get; }
string Describe();
}
// Invoice and Subscription as declared in the main example.
// Output:
// invoice INV-7: 50
// monthly subscription: 9One interface built on another
An interface can list other interfaces after a colon, exactly as a class does. Use it when a bigger promise is a smaller promise plus extra members, so code that only needs the smaller one can still accept your type.
interface IDrawable
{
void Draw();
}
// IShape inherits IDrawable. Implementing IShape means implementing both.
interface IShape : IDrawable
{
double Area { get; }
}
class Square : IShape
{
private readonly double _side;
public Square(double side) => _side = side;
public double Area => _side * _side;
public void Draw() => Console.WriteLine($"square of area {Area}");
}
// A Square can be stored in an IShape variable or an IDrawable variable.
IShape shape = new Square(3);
IDrawable drawable = shape;
drawable.Draw();
// Output:
// square of area 9The same promise kept two different ways
Two classes with nothing in common keep the same one-member promise. Swapping a predictable implementation in for the real one is how most tests avoid depending on real counters, clocks and databases.
interface IIdSource
{
int Next();
}
class Counter : IIdSource
{
private int _value;
// Each call moves the counter on by one.
public int Next() => ++_value;
}
class FixedIds : IIdSource
{
private readonly int[] _ids = { 10, 20 };
private int _index;
// A predictable source, useful in tests.
public int Next() => _ids[_index++];
}
IIdSource source = new FixedIds();
Console.WriteLine(source.Next());
Console.WriteLine(source.Next());
// Output:
// 10
// 20
Discussion