In der Welt der objektorientierten Programmierung bietet die Sprache C# zahlreiche Funktionen und Konstrukte, um sauberen und effizienten Code zu schreiben. Eine solche Funktion sind sogenannte "sealed" (versiegelte) Objekte, die in C# verwendet werden können. Diese versiegelten Objekte bieten eine Reihe von Vorteilen, die bei der Entwicklung von robustem und sicheren Code von unschätzbarem Wert sind.
Was sind versiegelte Objekte?
Versiegelte Objekte sind solche, die nicht von anderen Klassen abgeleitet werden können. In C# werden sie durch das Schlüsselwort "sealed" markiert. Indem man eine Klasse oder Methode als "sealed" deklariert, signalisiert man dem Compiler, dass diese nicht weiter abgeleitet oder überschrieben werden können.
Vorteile von versiegelten Objekten:
Sicherheit und Stabilität: Indem man eine Klasse als versiegelt markiert, verhindert man, dass andere Klassen davon erben können. Dies hilft, unerwünschte Änderungen oder unerwartetes Verhalten zu verhindern, indem man die Kontrolle über den Zustand und das Verhalten des Objekts behält.
Verbesserung der Leistung: Versiegelte Klassen ermöglichen es dem Compiler, bestimmte Optimierungen vorzunehmen, da er davon ausgehen kann, dass keine weiteren Klassen von dieser abgeleitet werden. Dies kann zu einer verbesserten Performance führen, insbesondere in Bereichen, in denen viele Instanzen dieser Klasse erstellt werden.
Klare API-Definition: Durch das Versiegeln von Klassen und Methoden kann man eine klare API-Definition sicherstellen, die für andere Entwickler leicht zu verstehen und zu verwenden ist. Dies hilft, Missverständnisse zu vermeiden und erleichtert die Wartung des Codes.
Verhinderung von Sicherheitslücken: Versiegelte Klassen können dazu beitragen, potenzielle Sicherheitslücken zu verhindern, indem sie verhindern, dass kritische Teile des Codes von externen Quellen manipuliert werden. Dies ist besonders wichtig in sicherheitskritischen Anwendungen und Umgebungen.
Einfachheit der Implementierung: Die Verwendung von versiegelten Objekten kann die Implementierung komplexer Systeme vereinfachen, da man sich auf eine klar definierte Schnittstelle verlassen kann, ohne sich um unerwartete Änderungen oder Seiteneffekte kümmern zu müssen.
Erhöhen von Leistung durch Seald Objekte
In vielen Artikeln wird darüber diskutiert ob seald einen Leistungsvorteil hat oder nicht. Ich habe für euch ein paar Benchmarks zur Verfügung gestellt um dies genauer unter die Luppe zu nehmen.
Erstmal die Test Klassen mit denen ich die Benchmarks ausgeführt habe:
public class BaseClass
{
public virtual void ExampleReturnVoid() {}
public virtual int ExampleReturnInt() => 0;
}
public class NotSealedClass : BaseClass
{
public override void ExampleReturnVoid() { }
public override int ExampleReturnInt() => 47;
}
public sealed class SealedClass : BaseClass
{
public override void ExampleReturnVoid() { }
public override int ExampleReturnInt() => 47;
}
public class NoVirtualClass
{
public void ExampleReturnVoid() {}
public int ExampleReturnInt() => 0;
}
Und das ist der Benchmark den ich ausgeführt habe:
[MemoryDiagnoser(false)]
public class Benchmarks
{
private readonly BaseClass _baseClass = new();
private readonly NotSealedClass _notSealedClass = new();
private readonly SealedClass _sealedClass = new();
private readonly NoVirtualClass _noVirtualClass = new();
[Benchmark]
public void BaseVoid() => _baseClass.ExampleReturnVoid();
[Benchmark]
public void NotSealedVoid() => _notSealedClass.ExampleReturnVoid();
[Benchmark]
public void SealedVoid() => _sealedClass.ExampleReturnVoid();
[Benchmark]
public void NoVirtualVoid() => _noVirtualClass.ExampleReturnVoid();
[Benchmark]
public int BaseInt() => _baseClass.ExampleReturnInt() + 10;
[Benchmark]
public int NotSealedInt() => _notSealedClass.ExampleReturnInt() + 10;
[Benchmark]
public int SealedInt() => _sealedClass.ExampleReturnInt()+ 10;
[Benchmark]
public int NoVirtualInt() => _noVirtualClass.ExampleReturnInt()+ 10;
[Benchmark]
public bool IsNotSealedType() => _baseClass is NotSealedClass;
[Benchmark]
public bool IsSealedType() => _baseClass is SealedClass;
}
In den Ergebnissen sieht man das sealed Methoden aufrufe einen kleinen Performance boost geben, oder wenn man den Vergleich Prozentual ansieht es schon ein großer unterschied ist. Wir sprechen hier aber von Prozessor-Zyklen die in einer Enterprise Anwendung bestimmt nicht sichtbar sind.
| Method | Mean | Error | StdDev | Median | Allocated |
|---|---|---|---|---|---|
| BaseVoid | 0.0223 ns | 0.0283 ns | 0.0251 ns | 0.0130 ns | - |
| NotSealedVoid | 0.2731 ns | 0.0262 ns | 0.0245 ns | 0.2679 ns | - |
| SealedVoid | 0.0077 ns | 0.0104 ns | 0.0092 ns | 0.0043 ns | - |
| NoVirtualVoid | 0.0104 ns | 0.0122 ns | 0.0114 ns | 0.0041 ns | - |
| BaseInt | 0.0931 ns | 0.0159 ns | 0.0141 ns | 0.0895 ns | - |
| NotSealedInt | 0.2537 ns | 0.0066 ns | 0.0062 ns | 0.2543 ns | - |
| SealedInt | 0.0124 ns | 0.0255 ns | 0.0213 ns | 0.0000 ns | - |
| NoVirtualInt | 0.0255 ns | 0.0274 ns | 0.0229 ns | 0.0227 ns | - |
| IsNotSealedType | 1.2686 ns | 0.0174 ns | 0.0146 ns | 1.2746 ns | - |
| IsSealedType | 0.2377 ns | 0.0116 ns | 0.0097 ns | 0.2353 ns | - |
Wenn man sich den IL Code anschaut, wird bei allen Methoden das callvirt aufgerufen.
IL_0000: ldarg.0
IL_0001: ldfld
IL_0006: callvirt
IL_000b: ldc.i4.s
IL_000d: add
IL_000e: ret
Fazit:
Versiegelte Objekte sind ein nützliches Werkzeug in der Werkzeugkiste eines C#-Entwicklers. Sie bieten eine Reihe von Vorteilen, darunter verbesserte Sicherheit, Leistung und Klarheit des Codes. Indem man bestimmte Klassen und Methoden als versiegelt markiert, kann man die Robustheit und Stabilität einer Anwendung verbessern und potenzielle Fehlerquellen minimieren. Daher ist es ratsam, versiegelte Objekte dort einzusetzen, wo eine klare Schnittstelle und Kontrolle über das Verhalten und den Zustand eines Objekts erforderlich sind.