
Voyons ce que dit le blog sur les changements Ă venir dans C # 8.0 (version de Visual Studio 2019 Preview 2):
«Les structures empilées uniquement sont apparues en C # 7.2. Ils sont extrêmement utiles, mais en même temps leur utilisation est étroitement liée à des restrictions, par exemple, l'impossibilité d'implémenter des interfaces. Désormais, les structures de liens peuvent être nettoyées à l’aide de la méthode Dispose sans utiliser l’interface IDisposable. "
Il en est ainsi: les structures ref empilées uniquement n'implémentent pas d'interfaces, sinon la probabilité de leur empaquetage se poserait. Par conséquent, ils ne peuvent pas implémenter IDisposable et nous ne pouvons pas utiliser ces structures dans l'instruction using:
class Program { static void Main(string[] args) { using (var book = new Book()) { Console.WriteLine("Hello World!"); } } } ref struct Book : IDisposable { public void Dispose() { } }
Tenter d'exécuter ce code entraînera une erreur de compilation :
Error CS8343 'Book': ref structs cannot implement interfaces
Cependant, maintenant, si nous ajoutons la méthode publique Dispose
à la structure de référence, l'instruction using
acceptera comme par magie et tout se compilera:
class Program { static void Main(string[] args) { using (var book = new Book()) { // ... } } } ref struct Book { public void Dispose() { } }
De plus, grâce aux modifications apportées à l'instruction elle-même, vous pouvez désormais utiliser using sous une forme plus courte (les soi-disant déclarations using
):
class Program { static void Main(string[] args) { using var book = new Book(); // ... } }
Mais ... pourquoi?
C'est une longue histoire, mais en général, le nettoyage explicite (finalisation déterministe) est préférable à implicite (finalisation non déterministe). C'est intuitif. Il est préférable d'effacer explicitement les ressources dès que possible (en appelant Close, Dispose ou en utilisant l'instruction), au lieu d'attendre le nettoyage implicite qui se produira «un jour» (lorsque l'environnement lui-même démarre les finaliseurs).
Par conséquent, lors de la création d'un type qui possède une certaine ressource, il est préférable de prévoir la possibilité d'un nettoyage explicite. En C #, c'est évidemment l'interface IDisposable
et sa méthode Dispose
.
Remarque N'oubliez pas que dans le cas des structures de référence, seul un nettoyage explicite est utilisé, car la définition de finaliseurs pour celles-ci est impossible.
Regardons un exemple illustratif du «wrapper habituel pour un pool de mémoire non managé». Il occupe le plus petit espace possible (le tas n'est pas du tout utilisé) précisément grâce à la structure de liaison destinée aux personnes obsédées par la performance:
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 } }
Étant donné que l'encapsuleur contient une ressource non gérée, nous utilisons la méthode Dispose pour la nettoyer après utilisation. Ainsi, l'exemple ressemble à ceci:
static void Main(string[] args) { var array = new UnmanagedArray<int>(10); Console.WriteLine(array[0]); array.Dispose(); }
Cela n'est pas pratique car vous devez vous rappeler d'appeler Dispose. En outre, c'est une décision douloureuse, car la gestion correcte des exceptions n'est pas applicable ici. Par conséquent, pour que Dispose soit appelé de l'intérieur, l'instruction using a été introduite. Cependant, auparavant, comme déjà mentionné, il était impossible de l'appliquer dans cette situation.
Mais en C # 8.0, vous pouvez tirer pleinement parti de l'instruction using:
static void Main(string[] args) { using (var array = new UnmanagedArray<int>(10)) { Console.WriteLine(array[0]); } }
Dans le même temps, le code est devenu plus concis grâce aux déclarations:
static void Main(string[] args) { using var array = new UnmanagedArray<int>(10); Console.WriteLine(array[0]); }
Les deux autres exemples ci-dessous (une grande partie du code omis par souci de concision) sont tirés du référentiel CoreFX.
Le premier exemple est la structure de référence ValueUtf8Converter, qui encapsule un tableau d'octets [] à partir d'un pool de tableaux:
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); } } }
Le deuxième exemple est RegexWriter, qui encapsule deux structures de référence ValueListBuilder qui doivent être effacées explicitement (car elles gèrent également les tableaux à partir du pool de tableaux):
internal ref struct RegexWriter { ... private ValueListBuilder<int> _emitted; private ValueListBuilder<int> _intStack; ... public void Dispose() { _emitted.Dispose(); _intStack.Dispose(); } }
Conclusion
Les structures référencées amovibles peuvent être considérées comme des types à faible espace dotés d'un VRAI destructeur, comme en C ++. Elle sera invoquée dès que l'instance correspondante dépasse la portée de l'instruction using (ou la portée dans le cas d'une déclaration using).
Bien sûr, ils ne deviendront pas soudainement populaires lors de l'écriture de programmes réguliers à vocation commerciale, mais si vous créez du code de bas niveau hautes performances, vous devez les connaître.
Et nous avons également un article sur notre conférence:
