Estructuras de referencia desechables en C # 8.0


Veamos qu茅 dice el blog sobre los pr贸ximos cambios en C # 8.0 (versi贸n de Visual Studio 2019 Preview 2):


鈥淟as estructuras de solo pila aparecieron en C # 7.2. Son extremadamente 煤tiles, pero al mismo tiempo su uso est谩 estrechamente relacionado con restricciones, por ejemplo, la incapacidad de implementar interfaces. Ahora las estructuras de enlace se pueden limpiar usando el m茅todo Dispose dentro de ellas sin usar la interfaz IDisposable ".


As铆 es: las estructuras de referencia solo de pila no implementan interfaces, de lo contrario, la probabilidad de su empaquetado surgir铆a. Por lo tanto, no pueden implementar IDisposable, y no podemos usar estas estructuras en la declaraci贸n de uso:


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

Intentar ejecutar este c贸digo dar谩 como resultado un error de compilaci贸n :


 Error CS8343 'Book': ref structs cannot implement interfaces 

Sin embargo, ahora si agregamos el m茅todo p煤blico Dispose a la estructura de referencia, la instrucci贸n using lo aceptar谩 m谩gicamente y todo se compilar谩:


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

Adem谩s, gracias a los cambios en la declaraci贸n en s铆, ahora puede usar el uso en una forma m谩s corta (las llamadas declaraciones de using ):


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

Pero ... porque?


Esta es una larga historia, pero en general, la limpieza expl铆cita (finalizaci贸n determinista) es preferible a la impl铆cita (finalizaci贸n no determinista). Esto es intuitivo. Es mejor borrar expl铆citamente los recursos lo antes posible (llamando a Cerrar, Eliminar o usando la declaraci贸n), en lugar de esperar la limpieza impl铆cita que suceder谩 "alg煤n d铆a" (cuando el entorno mismo inicie los finalizadores).


Por lo tanto, al crear un tipo que posee un determinado recurso, es mejor prever la posibilidad de limpieza expl铆citamente. En C #, esta es obviamente la interfaz IDisposable y su m茅todo Dispose .


Nota No olvide que en el caso de las estructuras de referencia, solo se utiliza una limpieza expl铆cita, ya que la definici贸n de finalizadores para ellas es imposible.


Veamos un ejemplo ilustrativo del habitual "contenedor para un grupo de memoria no administrado". Ocupa el espacio m谩s peque帽o posible (el mont贸n no se usa en absoluto) precisamente gracias a la estructura de enlace destinada a personas obsesionadas con el rendimiento:


 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 } } 

Como el contenedor contiene un recurso no administrado, utilizamos el m茅todo Dispose para limpiarlo despu茅s de su uso. Entonces el ejemplo se parece a esto:


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

Esto es inconveniente porque debe recordar llamar a Dispose. Adem谩s, esta es una decisi贸n dolorosa, ya que manejar excepciones correctamente no es aplicable aqu铆. Por lo tanto, para que se llame a Dispose desde dentro, se introdujo la instrucci贸n using. Sin embargo, antes, como ya se mencion贸, era imposible aplicarlo en esta situaci贸n.


Pero en C # 8.0, puede aprovechar al m谩ximo la declaraci贸n de uso:


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

Al mismo tiempo, el c贸digo se ha vuelto m谩s conciso gracias a las declaraciones:


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

Los otros dos ejemplos a continuaci贸n (gran parte del c贸digo omitido por brevedad) se toman del repositorio CoreFX.


El primer ejemplo es la estructura de referencia ValueUtf8Converter, que envuelve un conjunto de bytes [] de un conjunto de conjuntos:


 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); } } } 

El segundo ejemplo es RegexWriter, que envuelve dos estructuras de referencia de ValueListBuilder que deben borrarse expl铆citamente (ya que tambi茅n administran matrices del conjunto de matrices):


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

Conclusi贸n


Las estructuras referenciadas removibles se pueden considerar como tipos de poco espacio que tienen un destructor REAL, como en C ++. Se invocar谩 tan pronto como la instancia correspondiente vaya m谩s all谩 del alcance de la declaraci贸n de uso (o alcance en el caso de una declaraci贸n de uso).


Por supuesto, de repente no se volver谩n populares cuando escriban programas regulares orientados al comercio, pero si est谩 creando c贸digo de alto rendimiento y bajo nivel, debe conocerlos.


Y tambi茅n tenemos un art铆culo sobre nuestra conferencia:


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


All Articles