Einweg-Ref-Strukturen in C # 8.0


Mal sehen, was der Blog über diese bevorstehende Änderung in C # 8.0 (Visual Studio 2019 Preview 2-Version) sagt:


„Nur-Stapel-Strukturen wurden in C # 7.2 angezeigt. Sie sind äußerst nützlich, aber gleichzeitig hängt ihre Verwendung eng mit Einschränkungen zusammen, beispielsweise der Unfähigkeit, Schnittstellen zu implementieren. Jetzt können Linkstrukturen mit der darin enthaltenen Dispose-Methode ohne Verwendung der IDisposable-Schnittstelle bereinigt werden. "


Es ist also so: Nur-Stapel-Ref-Strukturen implementieren keine Schnittstellen, da sonst die Wahrscheinlichkeit ihrer Verpackung entstehen würde. Daher können sie IDisposable nicht implementieren, und wir können diese Strukturen nicht in der using-Anweisung verwenden:


class Program { static void Main(string[] args) { using (var book = new Book()) { Console.WriteLine("Hello World!"); } } } ref struct Book : IDisposable { public void Dispose() { } } 

Der Versuch, diesen Code auszuführen, führt zu einem Kompilierungsfehler ::


 Error CS8343 'Book': ref structs cannot implement interfaces 

Wenn wir nun jedoch die public Dispose Methode zur Referenzstruktur hinzufügen, akzeptiert die using Anweisung sie auf magische Weise und alles wird kompiliert:


 class Program { static void Main(string[] args) { using (var book = new Book()) { // ... } } } ref struct Book { public void Dispose() { } } 

Darüber hinaus können Sie dank Änderungen in der Anweisung selbst jetzt using in einer kürzeren Form verwenden (die sogenannten using Deklarationen):


 class Program { static void Main(string[] args) { using var book = new Book(); // ... } } 

Aber ... warum?


Dies ist eine lange Geschichte, aber im Allgemeinen ist eine explizite Bereinigung (deterministische Finalisierung) der impliziten (nicht deterministische Finalisierung) vorzuziehen. Das ist intuitiv. Es ist besser, die Ressourcen so schnell wie möglich explizit zu löschen (indem Sie Close, Dispose aufrufen oder die Anweisung verwenden), anstatt auf die implizite Bereinigung zu warten, die "eines Tages" stattfinden wird (wenn die Umgebung selbst die Finalizer startet).


Wenn Sie einen Typ erstellen, der eine bestimmte Ressource besitzt, ist es daher besser, die Möglichkeit einer expliziten Reinigung vorzusehen. In C # ist dies offensichtlich die IDisposable Schnittstelle und ihre Dispose Methode.


Hinweis Vergessen Sie nicht, dass bei Referenzstrukturen nur eine explizite Reinigung verwendet wird, da die Definition von Finalisierern für sie unmöglich ist.


Schauen wir uns ein anschauliches Beispiel für den üblichen „Wrapper für einen nicht verwalteten Speicherpool“ an. Es nimmt den kleinstmöglichen Platz ein (der Haufen wird überhaupt nicht verwendet), gerade dank der Verbindungsstruktur, die für leistungsbegeisterte Personen gedacht ist:


 public unsafe ref struct UnmanagedArray<T> where T : unmanaged { private T* data; public UnmanagedArray(int length) { data = // get memory from some pool } public ref T this[int index] { get { return ref data[index]; } } public void Dispose() { // return memory to the pool } } 

Da der Wrapper eine nicht verwaltete Ressource enthält, verwenden wir die Dispose-Methode, um sie nach der Verwendung zu bereinigen. Das Beispiel sieht also ungefähr so ​​aus:


 static void Main(string[] args) { var array = new UnmanagedArray<int>(10); Console.WriteLine(array[0]); array.Dispose(); } 

Dies ist unpraktisch, da Sie sich daran erinnern müssen, Dispose aufzurufen. Dies ist auch eine schmerzhafte Entscheidung, da hier die ordnungsgemäße Behandlung von Ausnahmen nicht anwendbar ist. Daher wurde die using-Anweisung eingeführt, damit Dispose von innen aufgerufen werden kann. Wie bereits erwähnt, war es jedoch früher unmöglich, es in dieser Situation anzuwenden.


In C # 8.0 können Sie jedoch die using-Anweisung voll ausnutzen:


 static void Main(string[] args) { using (var array = new UnmanagedArray<int>(10)) { Console.WriteLine(array[0]); } } 

Gleichzeitig ist der Code dank der folgenden Erklärungen präziser geworden:


 static void Main(string[] args) { using var array = new UnmanagedArray<int>(10); Console.WriteLine(array[0]); } 

Die beiden anderen folgenden Beispiele (ein Großteil des Codes wurde der Kürze halber weggelassen) stammen aus dem CoreFX-Repository.


Das erste Beispiel ist die ValueUtf8Converter-Referenzstruktur, die ein Byte [] -Array aus einem Array-Pool umschließt:


 internal ref struct ValueUtf8Converter { private byte[] _arrayToReturnToPool; ... public ValueUtf8Converter(Span<byte> initialBuffer) { _arrayToReturnToPool = null; } public Span<byte> ConvertAndTerminateString(ReadOnlySpan<char> value) { ... } public void Dispose() { byte[] toReturn = _arrayToReturnToPool; if (toReturn != null) { _arrayToReturnToPool = null; ArrayPool<byte>.Shared.Return(toReturn); } } } 

Das zweite Beispiel ist RegexWriter, das zwei ValueListBuilder-Referenzstrukturen umschließt, die explizit gelöscht werden müssen (da sie auch Arrays aus dem Array-Pool verwalten):


 internal ref struct RegexWriter { ... private ValueListBuilder<int> _emitted; private ValueListBuilder<int> _intStack; ... public void Dispose() { _emitted.Dispose(); _intStack.Dispose(); } } 

Fazit


Entfernbare referenzierte Strukturen können als Low-Space-Typen mit einem REAL-Destruktor wie in C ++ betrachtet werden. Sie wird aufgerufen, sobald die entsprechende Instanz den Bereich der using-Anweisung (oder den Bereich bei einer using-Deklaration) überschreitet.


Natürlich werden sie beim Schreiben von regulären, kommerziell ausgerichteten Programmen nicht plötzlich populär, aber wenn Sie leistungsstarken Code auf niedriger Ebene erstellen, sollten Sie sie kennen.


Und wir haben auch einen Artikel über unsere Konferenz:


Source: https://habr.com/ru/post/de447014/


All Articles