簡體   English   中英

為什么我們需要密封類?

[英]Why do we need sealed classes?

我知道密封類的功能。 它不可繼承。 但我的問題是為什么我們需要一個密封類? 如果不是繼承屬性和方法是動機,為什么不將它們聲明為私有?

首先,讓我們從定義開始; 密封是一個修飾符,如果應用於類,則使其不可繼承,如果應用於虛擬方法或屬性,則使其不可超越。

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 編譯的代碼(最后一個)。

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();
}

軍用代碼

.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

編譯器的優化使密封和非密封分類的性能差異如此之低,以至於我們談論的是處理器圈,因此與大多數應用程序無關。

您可以將成員設為公開,以便類外的代碼可以使用它們。 無論是否存在繼承,這都可能發生。

一個類可能需要被密封的原因是繼承需要被設計。 類的作者必須考慮繼承代碼將以何種方式與基類代碼交互。

在我看來,在 C# 語言中使類默認可繼承是錯誤的。 Java 走得更遠,默認方法是virtual ,這在許多人看來是一個設計錯誤。

1.在一個實現安全功能的類上,使得原始對象不能被“冒充”。

2.更一般地說,我最近與微軟的一個人交流,他告訴我他們試圖將繼承限制在真正有意義的地方,因為如果不加以處理,它會在性能方面變得昂貴。 Sealed 關鍵字告訴 CLR 沒有進一步向下查找方法的類,這會加快速度。

在當今市場上的大多數性能增強工具中,您會發現一個復選框,用於密封所有未繼承的類。 不過要小心,因為如果您想通過 MEF 允許插件或程序集發現,您會遇到問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM