Abstract classes and interfaces
↑Examples are independent C# snippets for modern .NET console projects unless noted otherwise. Class declarations and top-level code can live in Program.cs when top-level statements come first.
At a glance
Section titled “At a glance”Abstraction describes what an object can do without exposing all implementation details. Abstract classes can share state; interfaces describe capabilities.
How it works
Section titled “How it works”Abstract classes
Section titled “Abstract classes”You cannot instantiate an abstract class directly. It can supply implementation and require derived classes to provide abstract members. C# still allows only one base class.
public abstract class Shape{ public abstract double Area(); public string Label() => $"Area = {Area()}";}public sealed class Circle : Shape{ public double Radius { get; init; } public override double Area() => Math.PI * Radius * Radius;}Interfaces and multiple interfaces
Section titled “Interfaces and multiple interfaces”An interface declares a contract for implementing types. One class can implement several interfaces, enabling combinations of independent capabilities without multiple class inheritance. Modern C# interfaces can also contain default implementations, but start with simple contracts.
public interface IDamageable { void TakeDamage(int amount); }public interface IResettable { void Reset(); }public class Target : IDamageable, IResettable{ private int health = 100; public void TakeDamage(int amount) => health -= amount; public void Reset() => health = 100;}Dependency inversion
Section titled “Dependency inversion”Code against a small interface when multiple implementations or test substitutes are useful. Do not add an interface for every class automatically. A clear concrete class can be simpler.
The following snippet uses IDamageable and Target from the previous example.
public static void Hit(IDamageable target){ target.TakeDamage(10);}Hit(new Target());Deep dive: decide between shared implementation and a contract
Section titled “Deep dive: decide between shared implementation and a contract”An abstract class can provide both a rule and shared code
Section titled “An abstract class can provide both a rule and shared code”You cannot instantiate an abstract class directly. It can declare abstract members that subclasses must implement (unless they are abstract themselves), plus ordinary fields, methods and constructors that descendants reuse. Choose it when different kinds of objects have a genuine common foundation and require shared implementation.
An interface describes a capability
Section titled “An interface describes a capability”An interface says which operations an implementation offers, not which class it must inherit from. A class can implement many interfaces, which is why a damageable door, destructible crate and player can all implement IDamageable even though they belong to unrelated class hierarchies. Modern C# interfaces can contain default implementations and static members, but their core value is still a stable callable contract.
IDamageable target = new TrainingDummy();target.TakeDamage(12);
public interface IDamageable{ void TakeDamage(int amount);}
public sealed class TrainingDummy : IDamageable{ public int Health { get; private set; } = 50; public void TakeDamage(int amount) => Health = Math.Max(0, Health - amount);}Why this helps testing and maintenance
Section titled “Why this helps testing and maintenance”Code that accepts an IDamageable parameter does not need to know whether it received a player or a dummy. A test can supply a small fake implementation and verify that damage was requested. This is dependency inversion in a practical form: the caller relies on the behavior it needs instead of constructing or referencing a specific implementation. Prefer small interfaces that describe a coherent capability, not huge interfaces with methods most implementers cannot support.
When and why
Section titled “When and why”Use an abstract class for shared base implementation and an interface for a role implemented by unrelated types.
Common mistake
Section titled “Common mistake”Interface members are not automatically methods that all code can call on any object; the reference type and visibility still matter.
Try it yourself
Section titled “Try it yourself”Define ISaveable with Save() and implement it in two unrelated classes.
Reveal a solution
interface ISaveable { void Save(); } followed by separate implementations.
