Запечатанные Классы в C#

Я знаю функциональность Запечатанного класса. Это не наследуется. Но мой вопрос: зачем нам запечатанный класс? Если мотивом является не наследование свойств и методов, почему бы просто не объявить их как частные?

2 ответа

Прежде всего, давайте начнем с определения; sealed - это модификатор, который при применении к классу делает его ненаследуемым, а при применении к виртуальным методам или свойствам делает их не подлежащими проверке.

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

Примером его использования является определение специализированного класса / метода или свойства, в котором потенциальные изменения могут заставить их перестать работать должным образом (например, класс Pens пространства имен 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 не может быть унаследован, его нельзя использовать в качестве базового класса, и, как следствие, абстрактный класс не может использовать модификатор sealed.

Также важно отметить, что структуры неявно запечатаны.

Спектакль

Чтобы действительно их увидеть, вам нужно проанализировать код e, скомпилированный 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();
}

Код 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  

Хотя создание объектов такое же, инструкции, выполняемые для вызова методов запечатанного и производного / базового классов, немного отличаются. После перемещения данных в регистры или ОЗУ (инструкция mov), вызов запечатанного метода, выполнение сравнения между dword ptr [ecx],ecx (инструкция cmp), а затем вызов метода, в то время как производный / базовый класс выполняет непосредственно метод..

Согласно отчету Торбьорна Гранлунда "Задержки выполнения инструкций и пропускная способность для процессоров AMD и Intel x86", скорость выполнения следующей инструкции в Intel Pentium 4 составляет:

  • mov: имеет задержку в 1 цикл, и процессор может поддерживать 2,5 инструкции за цикл этого типа
  • cmp: имеет задержку в 1 цикл, и процессор может поддерживать 2 инструкции за цикл этого типа

Ссылка: https://gmplib.org/~tege/x86-timing.pdf

Оптимизация компиляторов сделала разницу между производительностью запечатанного и незапечатанного классов настолько низкой, что мы говорим о кругах процессора и по этой причине не имеют отношения к большинству приложений.

1. В классе, который реализует функции безопасности, так что исходный объект не может быть "олицетворен".

2. В целом, я недавно обменялся с человеком в Microsoft, который сказал мне, что они пытались ограничить наследование теми местами, где оно действительно имело смысл, потому что оно становится дорогим с точки зрения производительности, если его не обрабатывать. Ключевое слово sealed сообщает CLR, что ниже нет класса для поиска методов, и это ускоряет процесс.

В настоящее время в большинстве инструментов для повышения производительности на рынке вы найдете флажок, который запечатает все ваши классы, которые не наследуются. Будьте осторожны, потому что, если вы хотите разрешить плагин или обнаружение сборки через MEF, у вас возникнут проблемы.

Вы можете оставить членов публичными, чтобы их мог использовать код вне класса. Это может произойти независимо от того, есть наследование или нет.

Причина, по которой класс может нуждаться в запечатывании, заключается в том, что наследование должно быть разработано. Автор класса должен учитывать, каким образом наследующий код будет взаимодействовать с кодом базового класса.

На мой взгляд, в языке C# ошибочно делать классы наследуемыми по умолчанию. Java пошла еще дальше и сделала методы virtual по умолчанию, который кажется многим ошибкой дизайна.

Другие вопросы по тегам