Interfaces are the backbone of testable .NET code and dependency injection; inheritance shows up across frameworks.
A class can inherit from one base class: class Dog : Animal. It gets the base members and can call base constructors with : base(name).
Polymorphism must be opted into. The base marks a method virtual, the derived class marks its version override. Without virtual, a same-named method in a subclass only hides the base one (compiler warning; use new to say it is intended), and calls through a base-typed variable still run the base version.
abstract classcannot be instantiated and may declareabstractmembers that subclasses must implement.sealedprevents further inheritance (also a small performance win).protectedmembers are visible to subclasses.
An interface declares a contract with no state: interface IShape { double Area(); }. A class can implement many interfaces and inherit only one class. Interfaces may contain default implementations and static members, but most stay as pure contracts named with an I prefix.
Prefer interfaces plus composition over deep class hierarchies. Code against IEnumerable<T>, IRepository or ILogger rather than concrete types so implementations can be swapped and mocked in tests; ASP.NET Core's dependency injection is built on this.
Use is and as (or pattern matching) to test the runtime type, but if you need many type checks, a virtual method is usually the better design.
Example
IShape[] shapes = [new Circle(1), new Square(2)];
foreach (var s in shapes)
Console.WriteLine($"{s.Name}: {s.Area():F2}");
// Circle: 3.14
// Square: 4.00
interface IShape { string Name { get; } double Area(); }
abstract class Shape : IShape
{
public virtual string Name => GetType().Name;
public abstract double Area();
}
sealed class Circle(double r) : Shape
{
public override double Area() => Math.PI * r * r;
}
sealed class Square(double side) : Shape
{
public override double Area() => side * side;
}Remember
- Single class inheritance, many interfaces
- Methods are non-virtual unless marked virtual; derived classes override
- abstract classes share code, interfaces define contracts
- Depend on interfaces to make code swappable and testable
Watch out for
- Forgetting virtual/override and getting the base implementation when calling through a base reference.