Las estructuras con campos o propiedades públicas mutables no son malas.
Los métodos de estructura (a diferencia de los establecedores de propiedades) que mutan "esto" son algo malos, solo porque .net no proporciona un medio para distinguirlos de los métodos que no lo hacen. Los métodos de estructura que no mutan "esto" deberían ser invocables incluso en estructuras de solo lectura sin necesidad de copia defensiva. Los métodos que mutan "esto" no deberían ser invocables en absoluto en estructuras de solo lectura. Dado que .net no quiere prohibir que los métodos de estructura que no modifican "esto" se invoquen en estructuras de solo lectura, pero no quiere permitir que se muten estructuras de solo lectura, copia defensivamente estructuras en estructuras de lectura solo contextos, posiblemente obteniendo lo peor de ambos mundos.
Sin embargo, a pesar de los problemas con el manejo de los métodos de auto mutación en contextos de solo lectura, las estructuras mutables a menudo ofrecen una semántica muy superior a los tipos de clases mutables. Considere las siguientes tres firmas de métodos:
struct PointyStruct {public int x, y, z;};
clase PointyClass {public int x, y, z;};
Método 1 vacío (PointyStruct foo);
Método nulo2 (ref PointyStruct foo);
Método 3 vacío (PointyClass foo);
Para cada método, responda las siguientes preguntas:
- Suponiendo que el método no usa ningún código "inseguro", ¿podría modificar foo?
- Si no existen referencias externas a 'foo' antes de llamar al método, ¿podría existir una referencia externa después?
Respuestas:
Pregunta 1::
Method1()
no (intención clara)
Method2()
: sí (intención clara)
Method3()
: sí (intención incierta)
Pregunta 2
Method1()
:: no
Method2()
: no (a menos que no sea seguro)
Method3()
: sí
Method1 no puede modificar foo y nunca obtiene una referencia. Method2 obtiene una referencia de corta duración para foo, que puede usar para modificar los campos de foo cualquier cantidad de veces, en cualquier orden, hasta que regrese, pero no puede persistir esa referencia. Antes de que el Método 2 regrese, a menos que use un código inseguro, todas y cada una de las copias que se hayan hecho de su referencia 'foo' habrán desaparecido. Method3, a diferencia del Method2, obtiene una referencia promiscuamente compartible para foo, y no se sabe qué podría hacer con él. Puede que no cambie en absoluto, puede cambiar y luego volver, o puede dar una referencia a otro hilo que podría mutarlo de alguna manera arbitraria en algún momento futuro arbitrario.
Las matrices de estructuras ofrecen una semántica maravillosa. Dado RectArray [500] de tipo Rectangle, es claro y obvio cómo, por ejemplo, copiar el elemento 123 en el elemento 456 y luego, un tiempo después, establecer el ancho del elemento 123 en 555, sin alterar el elemento 456. "RectArray [432] = RectArray [321 ]; ...; RectArray [123] .Width = 555; ". Saber que Rectángulo es una estructura con un campo entero llamado Ancho le dirá a uno todo lo que uno necesita saber sobre las declaraciones anteriores.
Ahora suponga que RectClass era una clase con los mismos campos que Rectangle y se deseaba realizar las mismas operaciones en un RectClassArray [500] de tipo RectClass. Quizás se supone que la matriz contiene 500 referencias inmutables preinicializadas a objetos RectClass mutables. en ese caso, el código apropiado sería algo así como "RectClassArray [321] .SetBounds (RectClassArray [456]); ...; RectClassArray [321] .X = 555;". Tal vez se supone que la matriz contiene instancias que no van a cambiar, por lo que el código apropiado sería más parecido a "RectClassArray [321] = RectClassArray [456]; ...; RectClassArray [321] = New RectClass (RectClassArray [321 ]); RectClassArray [321] .X = 555; " Para saber lo que se supone que debe hacer, habría que saber mucho más sobre RectClass (por ejemplo, ¿admite un constructor de copias, un método de copia, etc.? ) y el uso previsto de la matriz. Nada tan limpio como usar una estructura.
Para estar seguros, desafortunadamente no hay una buena manera para que cualquier clase de contenedor que no sea una matriz ofrezca la semántica limpia de una matriz de estructura. Lo mejor que se podría hacer, si se quisiera indexar una colección con, por ejemplo, una cadena, probablemente sería ofrecer un método genérico "ActOnItem" que aceptara una cadena para el índice, un parámetro genérico y un delegado que se pasaría por referencia tanto el parámetro genérico como el elemento de colección. Eso permitiría casi la misma semántica que las matrices de estructura, pero a menos que las personas vb.net y C # puedan ser perseguidas para ofrecer una buena sintaxis, el código tendrá un aspecto torpe incluso si es un rendimiento razonable (pasar un parámetro genérico sería permitir el uso de un delegado estático y evitaría la necesidad de crear instancias de clase temporales).
Personalmente, estoy molesto por el odio Eric Lippert et al. vomitar con respecto a los tipos de valores mutables. Ofrecen una semántica mucho más limpia que los tipos de referencia promiscuos que se utilizan por todas partes. A pesar de algunas de las limitaciones con el soporte de .net para los tipos de valor, hay muchos casos en los que los tipos de valor mutable encajan mejor que cualquier otro tipo de entidad.
int
s mutablesbool
, y todos los demás tipos de valor son malvados. Hay casos de mutabilidad e inmutabilidad. Esos casos dependen del papel que juegan los datos, no del tipo de asignación / intercambio de memoria.