c# .net struct immutability mutable

c# - ¿Por qué System.Windows.Point & System.Windows.Vector es mutable?



.net struct (3)

Dado que las estructuras mutables generalmente se consideran malvadas (por ejemplo, ¿ por qué las estructuras mutables son "malas"? ), ¿Hay algún beneficio potencial que podría haber impulsado a los diseñadores de .NET Framework a hacer System.Windows.Point y System.Windows.Vector ¿mudable?

Me gustaría entender esto para poder decidir si tendría sentido hacer mudables mis propias estructuras similares (si es que alguna vez las tuviera). Es posible que la decisión de hacer que Point and Vector mutable sea solo un error de juicio, pero si hubiera una buena razón (por ejemplo, un beneficio de rendimiento), me gustaría entender de qué se trataba.

Sé que he tropezado con la implementación del método Vector.Normalize() varias veces porque, sorpresa (!), No devuelve un Vector nuevo. Simplemente altera el vector actual.

Siempre pienso que debería funcionar así:

var vector = new Vector(7, 11); var normalizedVector = vector.Normalize(); // Bzzz! Won''t compile

Pero en realidad funciona así:

var vector = new Vector(7, 11); vector.Normalize(); // This compiles, but now I''ve overwritten my original vector

... entonces, parece que la inmutabilidad es una buena idea simplemente para evitar confusiones, pero de nuevo, tal vez valga la pena esa posible confusión en algunos casos.


Estos tipos se encuentran en el espacio de nombres System.Windows y generalmente se utilizan en aplicaciones WPF. El marcado XAML de una aplicación es una gran parte del marco, por lo que para muchas cosas, necesitan una forma de expresarse utilizando XAML. Desafortunadamente, no hay forma de invocar constructores sin parámetros utilizando WPF XAML (pero es posible en XAML suelto) por lo que tratar de llamar a un constructor con los argumentos apropiados para inicializarlo no sería posible. Solo puede establecer los valores de las propiedades del objeto de forma natural, estas propiedades deben ser mutables.

¿Esto es malo? Para estos tipos, yo diría que no. Son solo para almacenar datos, nada más. Si quería obtener el tamaño que deseaba tener Window , DesiredSize a DesiredSize para obtener el Size objeto que representa el tamaño que deseaba. No tiene la intención de "cambiar el tamaño deseado" alterando las propiedades Width o Height del objeto Size que obtiene, puede cambiar el tamaño al proporcionar un nuevo objeto Size . Mirarlo de esta manera es mucho más natural, creo.

Si estos objetos fueran más complejos e hicieran operaciones más complicadas o tuvieran estado, entonces sí, no querría hacer estos tipos ni mutables ni estructurales. Sin embargo, dado que son casi tan simples y básicos como es posible (esencialmente un POD), las estructuras serían apropiadas aquí.


Posibilidades:

  1. Parecía una buena idea en ese momento para alguien que no consideraba los casos de uso donde mordía a la gente. List<T>.Enumerator es una estructura mutable que se utilizó porque parecía una buena idea en ese momento aprovechar los micro-opts que a menudo sucederían. Es casi el símbolo de que las estructuras mutables sean "malvadas" ya que son más de unas pocas las que muerden. Aún así, parecía una buena idea para alguien en ese momento ...
  2. Pensaban en los inconvenientes, pero tenían algún caso de uso conocido en el que las diferencias de rendimiento iban a favor de struct (no siempre) y se consideraban importantes.
  3. No consideraron que las estructuras son malvadas. "Evil" es una opinión acerca de que los lados bajos superan las ventajas, no es un hecho demostrable, y no todos tienen que estar de acuerdo con algo, incluso si Eric Lippert y Jon Skeet lo dicen. Personalmente, creo que no son malvados, simplemente no se los comprende; pero, de nuevo, el mal a menudo es más fácil de tratar que el mal interpretado por un programador, por lo que en realidad es peor ...;) Tal vez los involucrados están en desacuerdo.

Dichos tipos son mutables porque, contrariamente a lo que algunas personas podrían decir, la semántica mutable del tipo de valor es útil . Hay algunos lugares donde .net intenta pretender que los tipos de valores deben tener la misma semántica que los tipos de referencia. Dado que la semántica de tipo de valor mutable es fundamentalmente diferente de la semántica de tipo de referencia mutable, simular que son los mismos causará problemas. Sin embargo, eso no los convierte en "malvados"; simplemente muestra un defecto en un modelo de objeto que supone que actuar sobre una copia de algo será semánticamente equivalente a actuar sobre el original. Verdadero si la cosa en cuestión es una referencia de objeto; generalmente cierto, pero con excepciones, si se trata de una estructura inmutable; falso si es una estructura mutable.

Una de las cosas bellas de las estructuras con campos expuestos es que su semántica se puede determinar fácilmente incluso mediante una simple inspección. Si uno tiene Point[100] PointArray , uno tiene 100 instancias distintas de Point . Si uno dice PointArray[4].X = 9; , eso cambiará un elemento de PointArray y no otro.

Supongamos que en lugar de usar struct Point , uno tuviera una clase mutable PointClass :

class PointClass {public int X; public int Y;};

¿Cuántas instancias de PointClass se almacenan en PointClass[100] PointClassArray ? ¿Hay alguna manera de decirlo? ¿La declaración PointClass[4].X = 9 afectará el valor de PointClass [2] .X? ¿Qué pasa con someOtherObject.somePoint.X ?

Si bien las colecciones .net no son adecuadas para el almacenamiento de estructuras mutables, no obstante, consideraría:

Dictionary<string, Point>; ... Point temp = myDict["George"]; temp.X = 9; myDict["George"] = temp;

tener una semántica relativamente clara, al menos en ausencia de problemas de enhebrado. Aunque considero desafortunado que las colecciones de .net no proporcionen un medio por el que simplemente se pueda decir myDict[lookupKey].X = 9; Todavía consideraría el código anterior como bastante claro y que se explica por sí mismo sin tener que saber nada sobre Point aparte del hecho de que tiene un campo entero público llamado X. Por el contrario, si uno tiene un Dictionary<PointClass> , sería No está claro qué se debe esperar de uno para cambiar el valor X asociado a "George". Quizás la instancia de PointClass asociada con George no se use en ningún otro lado, en cuyo caso uno simplemente puede escribir el campo apropiado. Por otro lado, también es posible que otra persona haya tomado una copia de MyDict["George"] con el fin de capturar los valores que contiene, y no espera que el objeto PointClass que ha capturado pueda cambiar.

Algunas personas podrían pensar que "Punto" debería ser una estructura inmutable, pero el efecto de una declaración como somePoint.X = 5; puede determinarse completamente sabiendo solo que somePoint es una variable de tipo Point , que a su vez es una estructura con un campo público público llamado X Si Point fuera una estructura inmutable, uno tendría que decir algo como somePoint = new Point(5, somePoint.Y); , lo que, además de ser más lento, requeriría examinar la estructura para determinar que todos sus campos se inicializan en el constructor, siendo X el primero e Y el segundo. ¿En qué sentido sería una mejora sobre somePoint.X = 5; ?

Por cierto, el mayor ''gotcha'' con estructuras mutables proviene del hecho de que no hay forma de que el sistema distinga los métodos de estructura que alteran ''esto'' de aquellos que no lo hacen. Una gran pena. Las soluciones temporales preferidas son utilizar funciones que devuelvan nuevas estructuras derivadas de las anteriores, o bien utilizar funciones estáticas que acepten parámetros de estructura "ref".