Abstract Class vs Sealed Class in C#
Abstract and sealed classes are opposites, which is exactly why interviewers like to ask about them together:
- An abstract class must be inherited. You can't create an object of it; it exists to be a base class.
- A sealed class cannot be inherited. You can create objects of it, but no class can derive from it.
In this article we build a small notification system (email and SMS), where each concept has a real job, then look at the actual compiler errors you get when you break the rules. All output and errors below come from compiling and running the code with .NET 8.
Abstract class: a base that must be completed
Every notification in our system follows the same steps: write a log line, deliver the message, confirm it was sent. Only the delivery differs between email and SMS. An abstract class captures the common steps once and forces each channel to supply its own delivery:
public abstract class Notification
{
// Abstract members: no body here; every child class MUST provide one
protected abstract string Channel { get; }
protected abstract void Deliver(string message);
// Normal (concrete) method: shared by all children, written once
public void Send(string message)
{
Console.WriteLine($"Sending via {Channel}...");
Deliver(message);
Console.WriteLine($"{Channel} sent.");
}
}
public class EmailNotification : Notification
{
private readonly string _to;
public EmailNotification(string to) => _to = to;
protected override string Channel => "Email";
protected override void Deliver(string message) => Console.WriteLine($" To {_to}: {message}");
}
Key points:
- The
abstractkeyword on the class means "this class is incomplete". abstractmembers have no body; a child class supplies it withoverride.- An abstract class can still have normal methods (
Send), fields, properties and constructors. That's the big difference from an interface: it can share real code. - The shared
Sendmethod callsDeliverwithout knowing whether it's email or SMS. This is the Template Method pattern.
Sealed class: a final implementation
SMS delivery has carrier-specific rules (message length, sender IDs) that we don't want anyone to change by inheriting and overriding. So we seal it:
public sealed class SmsNotification : Notification
{
private readonly string _phone;
public SmsNotification(string phone) => _phone = phone;
protected override string Channel => "SMS";
protected override void Deliver(string message) => Console.WriteLine($" To {_phone}: {message}");
}
// A small utility that should never be extended
public sealed class OtpService
{
public string Generate() => Random.Shared.Next(100000, 1000000).ToString();
}
A sealed class can inherit from another class (here SmsNotification inherits from the abstract Notification); it just can't be inherited from.
Using them together
List<Notification> notifications =
[
new EmailNotification("asha@example.com"),
new SmsNotification("9876543210")
];
foreach (var n in notifications)
n.Send("Your order #1042 has been shipped.");
var otp = new OtpService();
Console.WriteLine($"OTP length: {otp.Generate().Length}");
The list's type is the abstract base, Notification, so the same loop works for every channel. Adding WhatsApp later means writing one new class; the loop doesn't change.
What the compiler does when you break the rules
1. Creating an object of an abstract class
var n = new Notification();
error CS0144: Cannot create an instance of the abstract type or interface 'Notification'
2. Forgetting to implement an abstract member
class Invoice : Notification { } // no Channel, no Deliver
error CS0534: 'Invoice' does not implement inherited abstract member 'Notification.Channel.get' error CS0534: 'Invoice' does not implement inherited abstract member 'Notification.Deliver(string)'
The compiler lists every abstract member that is missing. The only way out is to implement them all, or to declare the child class abstract too.
3. Inheriting from a sealed class
class PremiumSms : SmsNotification { }
error CS0509: 'PremiumSms': cannot derive from sealed type 'SmsNotification'
4. Giving an abstract method a body
abstract class Report
{
public abstract void Print() { }
}
error CS0500: 'Report.Print()' cannot declare a body because it is marked abstract
5. Making a class both abstract and sealed
abstract sealed class Helper { }
error CS0418: 'Helper': an abstract type cannot be sealed or static
It makes no sense: abstract means "must be inherited" and sealed means "can't be inherited", so nothing could ever use the class.
Sealed methods
sealed can also be used on an overridden method, to stop further overriding down the inheritance chain while still allowing the class itself to be inherited:
public class EmailNotification : Notification
{
// Children of EmailNotification can't override Deliver again
protected sealed override void Deliver(string message) { ... }
}
You can only seal a method that is an override. Sealing an ordinary method is an error, because it was never virtual in the first place:
class Base
{
public sealed void Log() { }
}
error CS0238: 'Base.Log()' cannot be sealed because it is not an override
Real examples from .NET
- Sealed:
System.Stringis sealed (typeof(string).IsSealedreturnsTrue), so nobody can change how strings behave by inheriting. Many framework types are sealed for safety and performance. - Abstract:
System.IO.Streamis abstract;FileStream,MemoryStreamandNetworkStreamcomplete it.DbConnectionand ASP.NET Core'sControllerBaseare abstract too. - Static classes such as
System.Mathare compiled as abstract and sealed at the IL level (typeof(Math)reportsIsAbstract=TrueandIsSealed=True): no objects and no inheritance. C# doesn't let you writeabstract sealedyourself (error CS0418); you writestatic classinstead.
Comparison table
| Point | Abstract class | Sealed class |
|---|---|---|
| Purpose | To be a base class that others complete | To be a final class that no one extends |
Can create objects with new? | No (CS0144) | Yes |
| Can be inherited? | Yes, that's its purpose | No (CS0509) |
| Can inherit from another class? | Yes | Yes |
| Abstract members | Allowed (children must override) | Not allowed |
| Normal methods, fields, constructors | Allowed | Allowed |
| Keyword on methods | abstract / virtual | sealed override to stop further overriding |
| Typical use | Shared logic plus required steps (template method, base repositories, base controllers) | Security-sensitive or final implementations, utility classes, small immutable types |
| .NET example | Stream, DbConnection | String |
When to use which
- Use an abstract class when several classes share real code and must each supply some specific part. If they share no code, an interface is usually better.
- Use a sealed class when a class isn't designed to be extended: inheriting could break its rules or security, or you simply haven't designed for it. Many teams seal classes by default and unseal only when there's a real need. The JIT compiler can also optimise calls on sealed types slightly better.
Interview questions
1. What is the difference between an abstract class and a sealed class?
An abstract class can't be instantiated and must be inherited; it can contain abstract members that children must implement. A sealed class can be instantiated but can't be inherited. They are opposites.
2. Can an abstract class be sealed?
No. The compiler reports CS0418, because a class that must be inherited and can't be inherited could never be used. (A static class is compiled as abstract + sealed in IL, but you write it as static.)
3. Can an abstract class have a constructor?
Yes. You can't call it with new directly, but it runs when a child class is created, to initialise the shared part of the object.
4. Can a sealed class inherit from another class?
Yes. sealed only stops other classes from inheriting from it. In our example, the sealed SmsNotification inherits from the abstract Notification.
5. What is a sealed method?
An overridden method marked sealed override, which can't be overridden again further down the hierarchy. Only override methods can be sealed (otherwise CS0238).
6. Abstract class or interface?
Choose an abstract class when related classes share implementation code and state. Choose an interface when unrelated classes need the same capability, or when a class needs several of them, since a class can inherit only one class but implement many interfaces.
Summary
- Abstract: can't be instantiated, must be inherited, may contain abstract members and shared code.
- Sealed: can be instantiated, can't be inherited.
- A class can't be both (CS0418); static classes are the special abstract + sealed case.
sealed overridestops a method from being overridden further.- Use abstract for shared base logic with required steps, and sealed for final implementations.