Inheritance and interfaces

C# · Practical · 14 min

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 class cannot be instantiated and may declare abstract members that subclasses must implement.
  • sealed prevents further inheritance (also a small performance win).
  • protected members 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.

Check yourself

  1. Base class has public void Speak() (not virtual). Derived declares new void Speak(). What does Animal a = new Dog(); a.Speak(); call?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: learn.microsoft.com/en-us/dotnet/csharp/fundamentals/object-oriented/inheritance