【问题标题】:Why do we need sealed classes?为什么我们需要密封类?
【发布时间】:2018-09-13 12:53:54
【问题描述】:

我知道 Sealed 类的功能。它不可继承。但我的问题是为什么我们需要一个密封类? 如果不继承属性和方法是动机,为什么不将它们声明为私有?

【问题讨论】:

    标签: c# c#-4.0 sealed-class


    【解决方案1】:

    首先,让我们从定义开始; sealed 是一个修饰符,如果应用于类,则使其不可继承,如果应用于虚拟方法或属性,则使其不可验证。

    public sealed class A { ... }
    public class B 
    {
        ...
        public sealed string Property { get; set; }
        public sealed void Method() { ... }
    }
    

    其用法的一个示例是定义一个专门的类/方法或属性,其中潜在的更改可能会使它们停止按预期工作(例如,System.Drawing 命名空间的 Pens 类)。

    ...
    namespace System.Drawing
    {
        //
        // Summary:
        //     Pens for all the standard colors. This class cannot be inherited.
        public sealed class Pens
        {
            public static Pen Transparent { get; }
            public static Pen Orchid { get; }
            public static Pen OrangeRed { get; }
            ...
        }
    }
    

    因为密封类不能被继承,所以不能用作基类,因此抽象类不能使用密封修饰符。

    同样重要的是要提到结构是隐式密封的。

    性能

    要真正看到它们,您需要分析 JIT 编译的 code(最后一个)。

    C# 代码

    public sealed class Sealed
    {
        public string Message { get; set; }
        public void DoStuff() { }
    }
    public class Derived : Base
    {
        public sealed override void DoStuff() { }
    }
    public class Base
    {
        public string Message { get; set; }
        public virtual void DoStuff() { }
    }
    static void Main()
    {
        Sealed sealedClass = new Sealed();
        sealedClass.DoStuff();
        Derived derivedClass = new Derived();
        derivedClass.DoStuff();
        Base BaseClass = new Base();
        BaseClass.DoStuff();
    }
    

    MIL 代码

    .method private hidebysig static void  Main() cil managed
    {
      .entrypoint
      // Code size       41 (0x29)
      .maxstack  8
      IL_0000:  newobj     instance void ConsoleApp1.Program/Sealed::.ctor()
      IL_0005:  callvirt   instance void ConsoleApp1.Program/Sealed::DoStuff()
      IL_000a:  newobj     instance void ConsoleApp1.Program/Derived::.ctor()
      IL_000f:  callvirt   instance void ConsoleApp1.Program/Base::DoStuff()
      IL_0014:  newobj     instance void ConsoleApp1.Program/Base::.ctor()
      IL_0019:  callvirt   instance void ConsoleApp1.Program/Base::DoStuff()
      IL_0028:  ret
    } // end of method Program::Main
    

    JIT 编译代码

    --- C:\Users\Ivan Porta\source\repos\ConsoleApp1\Program.cs --------------------
            {
    0066084A  in          al,dx  
    0066084B  push        edi  
    0066084C  push        esi  
    0066084D  push        ebx  
    0066084E  sub         esp,4Ch  
    00660851  lea         edi,[ebp-58h]  
    00660854  mov         ecx,13h  
    00660859  xor         eax,eax  
    0066085B  rep stos    dword ptr es:[edi]  
    0066085D  cmp         dword ptr ds:[5842F0h],0  
    00660864  je          0066086B  
    00660866  call        744CFAD0  
    0066086B  xor         edx,edx  
    0066086D  mov         dword ptr [ebp-3Ch],edx  
    00660870  xor         edx,edx  
    00660872  mov         dword ptr [ebp-48h],edx  
    00660875  xor         edx,edx  
    00660877  mov         dword ptr [ebp-44h],edx  
    0066087A  xor         edx,edx  
    0066087C  mov         dword ptr [ebp-40h],edx  
    0066087F  nop  
                Sealed sealedClass = new Sealed();
    00660880  mov         ecx,584E1Ch  
    00660885  call        005730F4  
    0066088A  mov         dword ptr [ebp-4Ch],eax  
    0066088D  mov         ecx,dword ptr [ebp-4Ch]  
    00660890  call        00660468  
    00660895  mov         eax,dword ptr [ebp-4Ch]  
    00660898  mov         dword ptr [ebp-3Ch],eax  
                sealedClass.DoStuff();
    0066089B  mov         ecx,dword ptr [ebp-3Ch]  
    0066089E  cmp         dword ptr [ecx],ecx  
    006608A0  call        00660460  
    006608A5  nop  
                Derived derivedClass = new Derived();
    006608A6  mov         ecx,584F3Ch  
    006608AB  call        005730F4  
    006608B0  mov         dword ptr [ebp-50h],eax  
    006608B3  mov         ecx,dword ptr [ebp-50h]  
    006608B6  call        006604A8  
    006608BB  mov         eax,dword ptr [ebp-50h]  
    006608BE  mov         dword ptr [ebp-40h],eax  
                derivedClass.DoStuff();
    006608C1  mov         ecx,dword ptr [ebp-40h]  
    006608C4  mov         eax,dword ptr [ecx]  
    006608C6  mov         eax,dword ptr [eax+28h]  
    006608C9  call        dword ptr [eax+10h]  
    006608CC  nop  
                Base BaseClass = new Base();
    006608CD  mov         ecx,584EC0h  
    006608D2  call        005730F4  
    006608D7  mov         dword ptr [ebp-54h],eax  
    006608DA  mov         ecx,dword ptr [ebp-54h]  
    006608DD  call        00660490  
    006608E2  mov         eax,dword ptr [ebp-54h]  
    006608E5  mov         dword ptr [ebp-44h],eax  
                BaseClass.DoStuff();
    006608E8  mov         ecx,dword ptr [ebp-44h]  
    006608EB  mov         eax,dword ptr [ecx]  
    006608ED  mov         eax,dword ptr [eax+28h]  
    006608F0  call        dword ptr [eax+10h]  
    006608F3  nop  
            }
    0066091A  nop  
    0066091B  lea         esp,[ebp-0Ch]  
    0066091E  pop         ebx  
    0066091F  pop         esi  
    00660920  pop         edi  
    00660921  pop         ebp  
    
    00660922  ret  
    

    虽然对象的创建是相同的,但调用密封类和派生/基类的方法所执行的指令略有不同。将数据移动到寄存器或RAM(mov指令)后,调用密封方法,执行dword ptr [ecx],ecx(cmp指令)之间的比较,然后调用该方法,而派生/基类直接执行该方法。 .

    根据 Torbj¨orn Granlund 撰写的报告,AMD 和 Intel x86 处理器的指令延迟和吞吐量,Intel Pentium 4 中以下指令的速度为:

    • mov:有 1 个周期作为延迟,处理器每个周期可以支持这种类型的 2.5 条指令
    • cmp:有 1 个周期作为延迟,处理器每个周期可以支持这种类型的 2 条指令

    链接https://gmplib.org/~tege/x86-timing.pdf

    编译器的优化使得密封类和非密封类的性能差异如此之低,以至于我们谈论的是处理器圈,因此与大多数应用程序无关。

    【讨论】:

      【解决方案2】:

      您可以将成员公开,以便类之外的代码可以使用它们。无论是否存在继承,这种情况都可能发生。

      一个类可能需要被密封的原因是需要设计继承。类的作者必须考虑继承代码与基类代码交互的方式。

      在我看来,在 C# 语言中让类默认可继承是一个错误。 Java 走得更远,默认使用 virtual 方法,这在许多人看来是一个设计错误。

      【讨论】:

      • 感谢您的简报。
      【解决方案3】:

      1.在实现安全特性的类上,使原对象不能被“模拟”。

      2.更一般地说,我最近与 Microsoft 的一个人交流,他告诉我他们试图将继承限制在真正有意义的地方,因为如果不加以处理,它会在性能方面变得昂贵。 sealed 关键字告诉 CLR 没有进一步查找方法的类,这加快了处理速度。

      在当今市场上的大多数性能增强工具中,您会发现一个复选框,它将密封您所有未继承的类。 不过要小心,因为如果你想通过 MEF 允许插件或程序集发现,你会遇到问题。

      【讨论】:

      • 谢谢@Waqas ..这真的很有意义:)
      猜你喜欢
      • 2019-06-09
      • 2020-12-17
      • 2014-06-30
      • 1970-01-01
      • 1970-01-01
      • 2014-06-18
      • 2017-02-26
      • 2011-04-03
      • 2017-07-27
      相关资源
      最近更新 更多