为什么应该默认将Class设为密封类?

前言

最近在 dotnet/sdk 上看到一个 Issue,它提出了一个有趣的要求:默认情况下将类设置为密封类(Sealed)

为什么应该默认将Class设为密封类?

什么是密封类?

默认情况下,类是开放的,这意味着它是可以被继承的。例如:.

class BaseClass
{
    public virtual string Method()
    {
        return "BaseClass.Method";
    }
}

class DerivedClass : BaseClass
{
    public override string Method()
    {
        return "DerivedClass.Method";
    }
}


class AnotherClass : DerivedClass
{
    //...
}

而密封类是一种特殊的类,它使用sealed修饰符阻止其他类继承自该类。例如:

sealed class SealedClass : BaseClass
{
    public override string Method()
    {
        return "SealedClass.Method";
    }
}

为什么应该默认将Class设为密封类?

那为什么要将类默认设为密封类呢?答案是——性能

Benchmark

首先创建一个控制台程序,并添加 BenchmarkDotNet 包。

然后创建一个性能测试类:

public class PerformanceBenchmark
{
    private readonly DerivedClass derivedClass = new DerivedClass();
    private readonly SealedClass sealedClass = new SealedClass();
    
    [Benchmark]
    public void DerivedClass_Method() => derivedClass.Method();
        
    [Benchmark]
    public void SealedClass_Method() => sealedClass.Method(); 
}

并且修改Program.cs文件,添加如下代码:

static void Main(string[] args)
{
    var summary = BenchmarkRunner.Run<PerformanceBenchmark>();
}

最后,运行程序,查看结果:

dotnet run -c Release

为什么应该默认将Class设为密封类?

可以看到,SealedClass_Method方法执行得明显快很多。

原理

为什么密封类比普通类执行得快呢?让我们看看 JIT 生成的代码:

为什么应该默认将Class设为密封类?

可以看到,当调用密封类中的重写方法时,是直接在密封类对象的内存地址上完成的。而当调用普通类中的重写方法时,却需要额外的mov指令,这是因为普通类的内存地址是不确定的,需要先获取到对象的类型,然后再从对应类型中获取方法。

总结

在本文中,我们了解了密封类有更好的性能。也就是说,如果你的类不会被继承,那么请将它设置为密封类。