为什么我们需要密封类?
Why do we need sealed classes?
我知道密封 class 的功能。它不可继承。但我的问题是为什么我们需要密封 Class?
如果不继承属性和方法是动机,为什么不将它们声明为私有的呢?
您可以保留成员 public,以便 class 之外的代码可以使用它们。无论是否有继承,都可能发生这种情况。
class 可能需要密封的原因是需要设计继承。 class 的作者必须考虑继承代码将以何种方式与基础 class 代码交互。
在我看来,在 C# 语言中让 classes 默认可继承是一个错误。 Java 走得更远,默认使用 virtual
方法,这在许多人看来是设计错误。
1.On一个实现安全特性的class,这样原来的对象就不能"impersonated".
2.More 总的来说,我最近与微软的一个人交流,他告诉我他们试图将继承限制在真正有意义的地方,因为如果不加以处理,它会在性能方面变得昂贵.
sealed 关键字告诉 CLR 没有 class 进一步向下寻找方法,这会加快速度。
在当今市场上的大多数性能增强工具中,您会发现一个复选框,用于密封所有未继承的 classes。
不过要小心,因为如果你想通过 MEF 允许插件或程序集发现,你将 运行 遇到问题。
首先,让我们从一个定义开始; sealed 是一个修饰符,如果应用于 class 则使其不可继承,如果应用于虚拟方法或属性则使其不可验证。
public sealed class A { ... }
public class B
{
...
public sealed string Property { get; set; }
public sealed void Method() { ... }
}
其用法的一个示例是定义一个专门的 class/method 或 属性,其中潜在的更改可以使它们按预期停止工作(例如,笔 class 的System.Drawing 命名空间)。
...
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; }
...
}
}
因为 sealed class 不能被继承,它不能用作基础 class 因此,抽象 class 不能使用 sealed 修饰符。
同样重要的是要提到结构是隐式密封的。
性能
要真正看到它们,您需要分析 JIT-Compiled 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
虽然对象的创建是相同的,但调用sealed方法的指令和derived/base class略有不同。将数据移动到寄存器或RAM(mov指令)后,调用密封方法,执行dword ptr [ecx],ecx(cmp指令)之间的比较,然后在derived/base [=70=时调用该方法] 直接执行方法..
根据 Torbj¨orn Granlund 撰写的报告,AMD 和 Intel x86 处理器的指令延迟和吞吐量,Intel Pentium 4 中以下指令的速度为:
- mov:有 1 个周期作为延迟,处理器每个周期可以支持 2.5 条此类指令
- cmp:有 1 个周期作为延迟,处理器可以在这种类型的每个周期维持 2 条指令
Link: https://gmplib.org/~tege/x86-timing.pdf
编译器的优化使得密封和非密封 classed 的性能差异如此之小,以至于我们谈论的是处理器圈,因此与大多数应用程序无关.
我知道密封 class 的功能。它不可继承。但我的问题是为什么我们需要密封 Class? 如果不继承属性和方法是动机,为什么不将它们声明为私有的呢?
您可以保留成员 public,以便 class 之外的代码可以使用它们。无论是否有继承,都可能发生这种情况。
class 可能需要密封的原因是需要设计继承。 class 的作者必须考虑继承代码将以何种方式与基础 class 代码交互。
在我看来,在 C# 语言中让 classes 默认可继承是一个错误。 Java 走得更远,默认使用 virtual
方法,这在许多人看来是设计错误。
1.On一个实现安全特性的class,这样原来的对象就不能"impersonated".
2.More 总的来说,我最近与微软的一个人交流,他告诉我他们试图将继承限制在真正有意义的地方,因为如果不加以处理,它会在性能方面变得昂贵. sealed 关键字告诉 CLR 没有 class 进一步向下寻找方法,这会加快速度。
在当今市场上的大多数性能增强工具中,您会发现一个复选框,用于密封所有未继承的 classes。 不过要小心,因为如果你想通过 MEF 允许插件或程序集发现,你将 运行 遇到问题。
首先,让我们从一个定义开始; sealed 是一个修饰符,如果应用于 class 则使其不可继承,如果应用于虚拟方法或属性则使其不可验证。
public sealed class A { ... }
public class B
{
...
public sealed string Property { get; set; }
public sealed void Method() { ... }
}
其用法的一个示例是定义一个专门的 class/method 或 属性,其中潜在的更改可以使它们按预期停止工作(例如,笔 class 的System.Drawing 命名空间)。
...
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; }
...
}
}
因为 sealed class 不能被继承,它不能用作基础 class 因此,抽象 class 不能使用 sealed 修饰符。
同样重要的是要提到结构是隐式密封的。
性能
要真正看到它们,您需要分析 JIT-Compiled 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
虽然对象的创建是相同的,但调用sealed方法的指令和derived/base class略有不同。将数据移动到寄存器或RAM(mov指令)后,调用密封方法,执行dword ptr [ecx],ecx(cmp指令)之间的比较,然后在derived/base [=70=时调用该方法] 直接执行方法..
根据 Torbj¨orn Granlund 撰写的报告,AMD 和 Intel x86 处理器的指令延迟和吞吐量,Intel Pentium 4 中以下指令的速度为:
- mov:有 1 个周期作为延迟,处理器每个周期可以支持 2.5 条此类指令
- cmp:有 1 个周期作为延迟,处理器可以在这种类型的每个周期维持 2 条指令
Link: https://gmplib.org/~tege/x86-timing.pdf
编译器的优化使得密封和非密封 classed 的性能差异如此之小,以至于我们谈论的是处理器圈,因此与大多数应用程序无关.