Interfaces

An interface declares a contract — members a type promises to provide — without (usually) providing implementation or state.

Concept

An interface declares a contract — members a type promises to provide — without (usually) providing implementation or state.

Declaration: public interface IEngine { void Start(); } (names start with I).

Implementation: a class or struct lists interfaces after its base class: class Ecu : IDiagnosable, IFlashable.

Multiple interfaces: a type can implement any number.

Explicit implementation: void IFileLogger.Write(...) — the member is accessible only through the interface, which resolves name clashes between interfaces.

Default interface members (C# 8): an interface can provide a default implementation (string Summary() => ...) that implementers may override. Called through the interface.

Interface inheritance: interface ISmartBattery : IDiagnosable, IChargeable.

Static abstract members (C# 11): interfaces can require static members (static abstract TSelf Zero { get; }) — the foundation of generic math.

Interface segregation: prefer several small, focused interfaces to one large one (Module 55).

Fields / state No (only static) No (one base class) Access modifiers on members

Public by default (others allowed since C# 8) Shared implementation of a family A capability many types can have

Why It Matters

Interfaces decouple code from implementations. They enable dependency injection, mocking in unit tests (IClock, ICanBus), plugins, and the entire collection/LINQ ecosystem (IEnumerable<T>).

Syntax

public interface IName { ReturnType Method(); Type Property { get; } }
public class C : BaseClass, IName, IOther { ... }
ReturnType IName.Method() { ... }       // explicit implementation

Example

// Interfaces: contracts that unrelated types can implement
List<IDiagnosable> units = [new Ecu("VCU"), new BatteryPack("PACK-01")];
foreach (IDiagnosable u in units)
    Console.WriteLine($"{u.Name}: {u.RunSelfTest()} | {u.Summary()}");

// Multiple interfaces
var pack = new BatteryPack("PACK-02");
IChargeable chargeable = pack;
chargeable.Charge(10);
Console.WriteLine($"{pack.Name} stored {pack.StoredKwh} kWh");

// Explicit interface implementation: only visible through the interface
var dual = new DualLogger();
((IFileLogger)dual).Write("to file");
((IConsoleLogger)dual).Write("to console");
// dual.Write("?");   // not accessible on the class itself

// Interface inheritance and static abstract members (generic math style)
Console.WriteLine($"Zero volts = {Zero<Volts>()}");

static T Zero<T>() where T : IHasZero<T> => T.Zero;

public interface IDiagnosable
{
    string Name { get; }
    bool RunSelfTest();
    string Summary() => $"{Name} self-test {(RunSelfTest() ? "passed" : "FAILED")}";  // default member
}

public interface IChargeable
{
    void Charge(double kwh);
}

public interface ISmartBattery : IDiagnosable, IChargeable   // interface inheritance
{
    double Stored
…

Expected Result

Ecu used the default Summary from the interface, while BatteryPack provided its own.

VCU: True | VCU self-test passed
PACK-01: True | PACK-01: 0 kWh stored
PACK-02 stored 10 kWh
[file] to file
[console] to console
Zero volts = 0 V

Common Mistakes

Fat interfaces that force implementers to write empty methods.

Adding a member to a published interface — breaks every implementer (unless it has a default implementation).

Expecting a default interface member to be callable on the class variable — it is only available through the interface.

Creating an interface for every class "just in case" — add one when there is a second implementation or a testing need.

Best Practices

Name interfaces for capabilities (IDiagnosable, IFlashable, IClock).

Keep them small; compose larger ones through interface inheritance.

Depend on interfaces in constructors (dependency injection, Module 45).

Use explicit implementation to hide rarely used or conflicting members.

Interview Relevance

Very high: "interface vs abstract class", "explicit interface implementation", "default interface methods", "why program to an interface?", "static abstract members".

Practice Questions

Define ICanBus with Send(CanFrame) and event Action<CanFrame> FrameReceived; implement a fake bus for tests.

Implement both IComparable<Vehicle> (by year) and IEquatable<Vehicle>.

Resolve a naming conflict between two interfaces with explicit implementation.

Write a generic Sum<T> using INumber<T> from System.Numerics.

Quick Quiz

Q1. (True/False) A class can implement several interfaces.

Answer: True. Q2. (Multiple choice) How is an explicitly implemented member called? A) Directly on the object B) Through a variable of the interface type C) With base. D) It cannot be called Answer: B.

Q3. (Predict the output) What does Ecu's Summary() print in the example?

Answer: VCU self-test passed — the interface's default implementation (verified).

Q4. (Multiple choice) Which can an interface NOT contain? A) Methods B) Properties C) Instance fields D) Default implementations Answer: C.

Coding Challenge

Define IClock with DateTime UtcNow { get; }, implement SystemClock and a FakeClock whose time can be set, and write a TokenValidator that uses IClock to decide whether a token (with an expiry time) is still valid.

var fake = new FakeClock { UtcNow = new DateTime(2026, 1, 1, 12, 0, 0, DateTimeKind.Utc) };
var validator = new TokenValidator(fake);
var expiry = new DateTime(2026, 1, 1, 12, 30, 0, DateTimeKind.Utc);
Console.WriteLine(validator.IsValid(expiry));
fake.UtcNow = fake.UtcNow.AddHours(1);
Console.WriteLine(validator.IsValid(expiry));

interface IClock { DateTime UtcNow { get; } }
class SystemClock : IClock { public DateTime UtcNow => DateTime.UtcNow; }
class FakeClock : IClock { public DateTime UtcNow { get; set; } }
class TokenValidator(IClock clock)
{
    public bool IsValid(DateTime expiresUtc) => clock.UtcNow < expiresUtc;
}