Запечатанные Классы в 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
по умолчанию, который кажется многим ошибкой дизайна.