Object-Oriented C# · Lesson 14

Abstract bases for incomplete shared behavior

Require specialized types to supply the missing part of a calculation.

How it works

An abstract class cannot be instantiated directly. It can contain ordinary shared behavior alongside abstract members that a concrete derived class must implement. This is useful when a family of objects shares a meaningful contract and some implementation structure, but there is no sensible standalone instance of the base concept.

The abstraction should describe what callers need rather than every detail that happens to be shared today. A base class that exposes too many protected fields can tightly couple derived classes to its internal representation. Prefer a small contract with clear invariants. An abstract member communicates a required capability, while a concrete shared member can build on that capability without knowing which derived type supplies it.

Walk through the example. The Shape base declares an abstract Area property and provides a shared Summary method that formats the area. Rectangle and Square supply different area calculations. The array holds Shape references, and Summary calls the correct Area implementation for each runtime object. The base class itself is never constructed, because it has no dimensions from which to calculate an area.

Watch the boundary. An abstract class is not automatically a better design than an interface or ordinary composition. Use it when shared implementation and a genuine base relationship both help the model. If unrelated types only need to expose an area-like capability, an interface may provide a narrower dependency. Avoid inheritance trees that exist only to save a few repeated lines of formatting code.

Practice independently. Add a Triangle implementation with an explicit base and height, and exercise it through the same Shape collection. Decide where positive-dimension validation belongs and test invalid inputs. Then refactor the shared Summary operation into an external formatter using an interface instead of a base class. Compare what the two designs allow to vary independently and choose the one that best matches the model.

A working example

using System;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

Shape[] shapes = { new Rectangle(3m, 4m), new Square(5m) };
foreach (Shape shape in shapes) Console.WriteLine(shape.Summary());
abstract class Shape
{
    public abstract decimal Area { get; }
    public string Summary() => Area.ToString("F2", CultureInfo.InvariantCulture);
}
sealed class Rectangle : Shape
{
    private readonly decimal width, height;
    public Rectangle(decimal width, decimal height) { this.width = width; this.height = height; }
    public override decimal Area => width * height;
}
sealed class Square : Shape
{
    private readonly decimal side;
    public Square(decimal side) { this.side = side; }
    public override decimal Area => side * side;
}

Expected output

12.00
25.00
Try this
Use an abstract base when both a shared contract and shared implementation are meaningful.