Sealed classes in .NET … but why?

While tinkering with C#, I was experimenting with declaring methods “virtual.” This effectively tells subclasses “you can override this method with your own implementation.” After some more tinkering, it seems you can also hide methods that are not declared virtual by prefixing them with “new.” But there is a difference:

using System;

public class A {
    public void method() {
        Console.WriteLine("A.method");
    }
    public virtual void method2() {
        Console.WriteLine("A.method2");
    }
}

public class B : A {
    new public void method() {
        Console.WriteLine("B.method");
    }
    public override void method2() {
        Console.WriteLine("B.method2");
    }
}

The compiler will not complain about this situation. Now pretend that we execute this: B b = new B(); b.method(); b.method2();. We get the output we expect:

B.method
B.method2

Now let’s try A b = new B(); b.method(); b.method2();. See the difference? This time we store the B in an A reference.

A.method
B.method2

Java users will be shocked at this behaviour. But B.method overrides A.method, right?

Wrong. It hides A.method. If we had said A b = new B(); (b as B).method(); we would get the output we expect. This behavior comes directly from C++: if a non-virtual method is “overridden” then which method gets called depends on the reference type used to call it!

This serves two purposes:

  1. If a method is not declared virtual, the CLR doesn’t have to waste time looking for overridden methods on child classes; it can jump directly to the method.
  2. The virtual modifier can be thought of as giving permission to override. If you target a non-virtual method you know it will behave the same way, so long as you are using the same reference type when invoking it.

Which brings me to the question of the day. C# (and any .NET language) supports the notion of a “sealed” class — one that cannot be derived from at all. Immutable classes, such as System.String, are sealed to prevent tampering that could aggravate developers or compromise the entire .NET security architecture.

But what if System.String weren’t sealed? Why, nothing! Almost. Methods overloaded from Object (such as ToString) are implicitly virtual — but if they were marked “sealed” in System.String they are effectively “un-virtualed!” Redefining ToString in a subclass would require “new” and would exhibit the same behavior as the example above. For example:

using System;

public class A {
    public virtual void Foo() {
        Console.WriteLine("A");
    }
}

public class B : A {
    public sealed override void Foo() {
        Console.WriteLine("B");
    }
}

public class C : B {
    new public void Foo() {
        Console.WriteLine("C");
    }
}

public class R {
    public static void Main() {
        C c = new C();
        B b = c;
        A a = b;

        a.Foo();
        b.Foo();
        c.Foo();
    }
}

This outputs the expected:

B
B
C

The benefit of un-sealing these classes is that we could subclass System.String to add our own properties, be able to pass this object around as if it were a string, and not sacrifice the integrity of its immutability. As long as none of System.String’s immutable information is protected or public, we can’t touch it.

Where the system treats it as a string, it is a string. Where we treat it as a subclass, we can extend its behavior how we like.

So why are there sealed classes in .NET? You tell me.

CategoriesC#

4 Replies to “Sealed classes in .NET … but why?”

  1. I am not sure if this is the correct answer, but I think this is not just because of the immutability that the System.String class is sealed. To improve performance, it is required that the CLR should know the exact layout of the fields defined within the String class (CLR accesses them directly, unlike other classes). Now, if someone derives a class from String and defines his/her own fields, it would be impossible for the CLR to make any assumptions about the internal layout of the class.

    Hope this helps. And if anyone thinks this is not the fact, I would be very happy to know the actual reason.

  2. @Jeevan:

    If the CLR does in fact optimize the layout of the fields across an entire hierarchy, it could certainly ensure that the position of the string fields remains the same.

  3. From the .NET documentation:
    “A sealed class cannot be used as a base class. For this reason, it cannot also be an abstract class. Sealed classes are primarily used to prevent derivation. Because they can never be used as a base class, some run-time optimizations can make calling sealed class members slightly faster.”

  4. @Rob: Actually, that’s not entirely true… “static class Foo” emits IL that is equivalent to “abstract sealed class Foo” — sealing an abstract class ensures that it can never be instantiated, even if reflection is used to invoke a private constructor.

Comments are closed.