Sealed Classes
Inheritance
Object-Oriented Programming
Class Design
Software Development

How to deal with a sealed class when I wanted to inherit and add properties

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Needing extra properties on a class that is sealed is a common design problem in enterprise codebases. Since inheritance is blocked by intent, the safe path is to extend behavior through composition instead of subclassing. This article shows practical patterns in C# that preserve the original contract and still let you add metadata and logic.

Why a Class Is Sealed in the First Place

Libraries seal classes to protect invariants, simplify versioning, and avoid unsupported overrides. If you force inheritance through workarounds, upgrades become fragile and behavior can drift from documentation. It is better to treat the sealed type as a stable dependency and build extension points around it.

A basic sealed type looks like this:

csharp
1public sealed class PaymentToken
2{
3    public string Value { get; }
4
5    public PaymentToken(string value)
6    {
7        if (string.IsNullOrWhiteSpace(value))
8            throw new ArgumentException("Token value is required", nameof(value));
9
10        Value = value;
11    }
12}

Because this type cannot be inherited, your extension model moves to wrappers, adapters, and helper APIs.

Add Properties With a Wrapper Object

The most direct approach is a wrapper that stores the sealed instance and your additional properties. This gives you explicit ownership over validation, serialization shape, and lifecycle.

csharp
1using System;
2
3public sealed class PaymentToken
4{
5    public string Value { get; }
6    public PaymentToken(string value) => Value = value;
7}
8
9public sealed class TrackedPaymentToken
10{
11    public PaymentToken Token { get; }
12    public DateTime CreatedAtUtc { get; }
13    public string SourceSystem { get; }
14    public string CorrelationId { get; }
15
16    public TrackedPaymentToken(
17        PaymentToken token,
18        DateTime createdAtUtc,
19        string sourceSystem,
20        string correlationId)
21    {
22        Token = token ?? throw new ArgumentNullException(nameof(token));
23        CreatedAtUtc = createdAtUtc;
24        SourceSystem = string.IsNullOrWhiteSpace(sourceSystem)
25            ? throw new ArgumentException("Source is required", nameof(sourceSystem))
26            : sourceSystem;
27        CorrelationId = string.IsNullOrWhiteSpace(correlationId)
28            ? throw new ArgumentException("Correlation id is required", nameof(correlationId))
29            : correlationId;
30    }
31}
32
33public static class Program
34{
35    public static void Main()
36    {
37        var token = new PaymentToken("abc123");
38        var tracked = new TrackedPaymentToken(token, DateTime.UtcNow, "checkout-api", "req-777");
39
40        Console.WriteLine($"Token: {tracked.Token.Value}");
41        Console.WriteLine($"Source: {tracked.SourceSystem}");
42        Console.WriteLine($"Correlation: {tracked.CorrelationId}");
43    }
44}

This pattern is usually enough when you need new state and can control call sites.

Use an Adapter Interface for Flexible Behavior

If your service layer needs abstraction, define an interface and expose the sealed type through an adapter. This keeps business logic independent from vendor classes and improves testability.

csharp
1using System;
2
3public sealed class PaymentToken
4{
5    public string Value { get; }
6    public PaymentToken(string value) => Value = value;
7}
8
9public interface ITokenInfo
10{
11    string RawValue { get; }
12    bool IsSandbox { get; }
13}
14
15public sealed class PaymentTokenAdapter : ITokenInfo
16{
17    private readonly PaymentToken _inner;
18
19    public PaymentTokenAdapter(PaymentToken inner)
20    {
21        _inner = inner ?? throw new ArgumentNullException(nameof(inner));
22    }
23
24    public string RawValue => _inner.Value;
25
26    public bool IsSandbox =>
27        RawValue.StartsWith("test_", StringComparison.OrdinalIgnoreCase);
28}
29
30public static class Program
31{
32    public static void Main()
33    {
34        ITokenInfo info = new PaymentTokenAdapter(new PaymentToken("test_987"));
35        Console.WriteLine($"Raw value: {info.RawValue}");
36        Console.WriteLine($"Sandbox token: {info.IsSandbox}");
37    }
38}

An adapter is especially useful when multiple token providers exist and your domain needs one uniform contract.

Add Utility Behavior With Extension Methods

Extension methods can make usage cleaner when you need computed behavior rather than stored properties. They are not substitutes for added state, but they are good for shared formatting and validation helpers.

csharp
1public static class PaymentTokenExtensions
2{
3    public static string Masked(this PaymentToken token)
4    {
5        if (token.Value.Length <= 4) return "****";
6        return new string('*', token.Value.Length - 4) + token.Value[^4..];
7    }
8}

Use wrappers for data, adapters for abstraction, and extension methods for convenience behavior. Combined thoughtfully, these patterns cover most sealed-class extension needs.

Common Pitfalls

  • Treating a wrapper as a drop-in subtype, which causes friction where exact base type signatures are required.
  • Hiding the original sealed object completely even though integration code still needs direct access.
  • Letting wrapper types accumulate unrelated fields and become giant transport objects.
  • Using extension methods to imitate stateful properties, which is impossible and misleading.
  • Skipping constructor validation on added properties, allowing invalid metadata to spread.

Summary

  • Sealed classes intentionally block inheritance, so extension should use composition patterns.
  • Wrapper types are the cleanest way to add new properties.
  • Adapter interfaces decouple domain logic from external sealed classes.
  • Extension methods are helpful for computed helpers, not instance state.
  • Respecting the sealed contract generally improves maintainability and upgrade safety.

Course illustration
Course illustration

All Rights Reserved.