2009-05-15 42 views
29

frecuencia leo que struct s deben ser inmutables - ¿no son por definición?¿Los tipos de valor son inmutables por definición?

¿Considera int ser inmutable?

int i = 0; 
i = i + 123; 

parece estar bien - tenemos una nueva int y asignamos de nuevo a i. ¿Qué hay de esto?

i++; 

De acuerdo, podemos considerarlo un atajo.

i = i + 1; 

Qué pasa con el structPoint?

Point p = new Point(1, 2); 
p.Offset(3, 4); 

¿Esto realmente mutar el punto (1, 2)? ¿No deberíamos pensar en ello como un atajo para lo siguiente con Point.Offset() devolviendo un nuevo punto?

p = p.Offset(3, 4); 

El trasfondo de este pensamiento es este: ¿cómo puede ser mutable un tipo de valor sin identidad? Tienes que mirarlo al menos dos veces para determinar si cambió. Pero, ¿cómo puedes hacer esto sin una identidad?

no quiero complicar el razonamiento acerca de esto considerando ref parámetros y el boxeo. También sé que p = p.Offset(3, 4); expresa la inmutabilidad mucho mejor que p.Offset(3, 4);. Pero la pregunta sigue siendo: ¿no son los tipos de valores inmutables por definición?

ACTUALIZACIÓN

Creo que hay al menos dos conceptos involucrados - la mutabilidad de una variable o campo y la mutabilidad del valor de una variable.

public class Foo 
{ 
    private Point point; 
    private readonly Point readOnlyPoint; 

    public Foo() 
    { 
     this.point = new Point(1, 2); 
     this.readOnlyPoint = new Point(1, 2); 
    } 

    public void Bar() 
    { 
     this.point = new Point(1, 2); 
     this.readOnlyPoint = new Point(1, 2); // Does not compile. 

     this.point.Offset(3, 4); // Is now (4, 6). 
     this.readOnlyPoint.Offset(3, 4); // Is still (1, 2). 
    } 
} 

En el ejemplo tenemos campos a, uno mutable e inmutable. Como un campo de tipo de valor contiene el valor completo, un tipo de valor almacenado en un campo inmutable también debe ser inmutable. Todavía estoy bastante sorprendido por el resultado: no esperaba que el campo de solo lectura permanezca sin modificaciones.

Variables (además de constantes) son Allways mutable, por lo tanto, implican ninguna restricción sobre la mutabilidad de los tipos de valor.


La respuesta no parece ser tan sencilla, por lo que voy a reformular la pregunta.

Dada la siguiente.

public struct Foo 
{ 
    public void DoStuff(whatEverArgumentsYouLike) 
    { 
     // Do what ever you like to do. 
    } 

    // Put in everything you like - fields, constants, methods, properties ... 
} 

le puede dar una versión completa de Foo y un ejemplo de uso - que puede incluir ref parámetros y el boxeo - de modo que no es posible volver a escribir todas las apariciones de

foo.DoStuff(whatEverArgumentsYouLike); 

con

foo = foo.DoStuff(whatEverArgumentsYouLike); 

Respuesta

34

Un objeto es inmutable, si su estado no cambia una vez que se ha creado el objeto .

Respuesta corta: No, los tipos de valores no son inmutables por definición. Tanto las estructuras como las clases pueden ser mutables o inmutables. Las cuatro combinaciones son posibles. Si una estructura o clase tiene campos públicos que no son de solo lectura, propiedades públicas con definidores o métodos que establecen campos privados, es mutable porque puede cambiar su estado sin crear una nueva instancia de ese tipo.


Respuesta larga: En primer lugar, la cuestión de la inmutabilidad sólo se aplica a estructuras o clases con campos o propiedades. Los tipos más básicos (números, cadenas y nulo) son intrínsecamente inmutables porque no hay nada (campo/propiedad) que cambiar sobre ellos. A 5 es un 5 es un 5. Cualquier operación en el 5 solo devuelve otro valor inmutable.

Puede crear estructuras mutables como System.Drawing.Point. Tanto X y Y tienen los emisores que modifican los campos de la struct:

Point p = new Point(0, 0); 
p.X = 5; 
// we modify the struct through property setter X 
// still the same Point instance, but its state has changed 
// it's property X is now 5 

Algunas personas parecen confundir immutablity con el hecho de que los tipos de valores se pasan por valor (de ahí su nombre) y no por referencia.

void Main() 
{ 
    Point p1 = new Point(0, 0); 
    SetX(p1, 5); 
    Console.WriteLine(p1.ToString()); 
} 

void SetX(Point p2, int value) 
{ 
    p2.X = value; 
} 

En este caso Console.WriteLine() escribe "{X=0,Y=0}". Aquí p1 no se modificó porque SetX() modificó p2 que es una copia de p1. Esto sucede porque p1 es un tipo de valor , no porque sea inmutable (no lo es).

¿Por qué debería tipos de valores ser inmutables? Muchas razones ... Ver this question. Principalmente se debe a que los tipos de valores mutables conducen a todo tipo de errores no tan obvios. En el ejemplo anterior, el programador podría haber esperado que p1 fuera (5, 0) después de llamar al SetX(). O imagine ordenar por un valor que luego puede cambiar. Entonces su colección ordenada ya no será ordenada como se esperaba. Lo mismo aplica para diccionarios y hashes. El Fabulous Eric Lippert (blog) ha escrito un whole series about immutability y por qué cree que es el futuro de C#. Here's one of his examples que le permite "modificar" una variable de solo lectura.


ACTUALIZACIÓN: tu ejemplo con:

this.readOnlyPoint.Offset(3, 4); // Is still (1, 2). 

es exactamente lo que Lippert se hace referencia en su puesto acerca de cómo modificar Variables de lectura. Offset(3,4) realmente modificó un Point, pero era una copia de , y nunca se asignó a nada, por lo que se pierde.

Y que es la razón por tipos de valores mutables son malos: Ellos le permiten piensa que va a modificar algo, a veces en realidad se está modificando una copia, lo que conduce a errores inesperados. Si Point fuera inmutable, Offset() tendría que devolver un nuevo Point, y no habría podido asignarlo al readOnlyPoint. Y luego vas al "Ah, sí, es de solo lectura por una razón.¿Por qué estaba tratando de cambiarlo? Lo bueno es que el compilador me detuvo ahora."


ACTUALIZACIÓN: Sobre su solicitud reformulada ... creo que sé lo que quieres decir En cierto modo, se puede. 'Pensar' de estructuras como internamente inmutable, que modificar una estructura es lo mismo que reemplazarla con una copia modificada. Por lo que sé, podría ser lo que la CLR hace internamente en la memoria. (Así es como funciona la memoria flash. No se pueden editar solo unos pocos bytes, necesitas leer un bloque entero de kilobytes en la memoria, modificar los pocos que quieras y volver a escribir todo el bloque). Sin embargo, incluso si fueran "internamente inmutables", eso es un detalle de implementación y para nosotros los desarrolladores como usuarios de estructuras (su interfaz o API, si se quiere), ellos puede cambiarse. No podemos ignorar ese hecho y "pensar en ellos como inmutables".

En un comentario, dijo "no puede tener una referencia al valor de campo o variable". Está asumiendo que cada variable de estructura tiene una copia diferente, de modo que modificar una copia no afecta a las demás. Eso no es del todo cierto. Las líneas marcadas a continuación no son reemplazables si ...

interface IFoo { DoStuff(); } 
struct Foo : IFoo { /* ... */ } 

IFoo otherFoo = new Foo(); 
IFoo foo = otherFoo; 
foo.DoStuff(whatEverArgumentsYouLike); // line #1 
foo = foo.DoStuff(whatEverArgumentsYouLike); // line #2 

líneas # 1 y # 2 no tienen los mismos resultados ... ¿Por qué? Porque foo y otherFoo se refieren a la misma instancia en caja de Foo. Lo que sea que haya cambiado en foo en la línea n. ° 1 se refleja en otherFoo. La línea n.º 2 reemplaza a foo con un nuevo valor y no hace nada al otherFoo (suponiendo que DoStuff() devuelve una nueva instancia de IFoo y no modifica el propio foo).

Foo foo1 = new Foo(); // creates first instance 
Foo foo2 = foo1; // create a copy (2nd instance) 
IFoo foo3 = foo2; // no copy here! foo2 and foo3 refer to same instance 

Modificación foo1 no afectará foo2 o foo3. Modificando foo2 se reflejará en foo3, pero no en foo1. Modificando foo3 se reflejará en foo2 pero no en foo1.

¿Confunde? Se adhiere a los tipos de valores inmutables y elimina la necesidad de modificar cualquiera de ellos.


ACTUALIZACIÓN: error tipográfico fijo en el primer ejemplo de código

+0

Agregué este comentario a varias respuestas antes. Podría reescribir p.X = 5; como p = p.SetX (5) ;. Si siempre puedo hacer esto -la semántica del tipo de valor puede permitir esto, pero no estoy seguro- podría considerar que la estructura es inmutable o equivalente a una estructura inmutable. Así que reformulé la pregunta: ¿puedo hacer esta transformación siempre? Si es así, esto implica que las estructuras son inmutables porque puedo reescribirlas de una manera que hace inmutable la inmutabilidad. –

+0

@Daniel: No estoy seguro de seguirte. Si puede hacer p.X = 5, el tipo es mutable. Si p2 = p1.SetX (5) no cambia p1, y no hay forma de cambiar p1, entonces es inmutable. Tenga en cuenta que p = p.SetX (5) está reemplazando el valor de p con uno nuevo, sin modificar el valor original. – Lucas

+0

Usted es absolutamente correcto. Y como p es un tipo de valor y no puede tener una referencia, no debería importar si modifica el valor almacenado en p o lo reemplaza con una versión modificada. Si encuentras un ejemplo donde esto importa, tal vez algo que involucre parámetros de ref, boxeo, o algo que ni siquiera he pensado (actualmente estoy pensando en las propiedades del tipo de valor), entonces estoy equivocado y las estructuras son mutables. Si siempre puedo transformar myStruct.ChangeStuff() en myStruct = myStruct.ChangeStuff(), entonces puedo considerar que las estructuras son inmutables. –

5

Puede escribir estructuras que son mutables, pero lo mejor es hacer que los tipos de valores sean inmutables.

Por ejemplo, DateTime siempre crea nuevas instancias al realizar cualquier operación. El punto es mutable y se puede cambiar.

Para responder a su pregunta: No, no son inmutables por definición, depende del caso si deben ser mutables o no. Por ejemplo, si deberían servir como claves del diccionario, deberían ser inmutables.

+0

Puede crear una estructura simple como struct Foo {public int Bar;}. La pregunta no es si puede hacer esto, pero si Foo es mutable o no. –

+0

La estructura System.Drawing.Point, por ejemplo, no es inmutable. – ChrisW

+1

Ok, no sabía que hay estructuras mutables en el marco. Corrija mi respuesta, gracias. –

9

La mutabilidad y los tipos de valores son dos cosas separadas.

La definición de un tipo como tipo de valor indica que el tiempo de ejecución copiará los valores en lugar de una referencia al tiempo de ejecución. La mutación, por otro lado, depende de la implementación, y cada clase puede implementarla como lo desee.

+0

Estoy al tanto de eso, pero la pregunta es si ser un tipo de valor implica ser inmutable. –

+1

No lo implica, ya que depende del usuario implementar la inmutabilidad. Puede tener una clase Point que sea inmutable, o implementar es tan mutable. – pgb

+0

Todavía creo que no puedes. ¿Podría dar un ejemplo donde la modificación de una estructura con myStruct.DoStuuf() no se puede explicar como myStruct = myStruct.DoStuff()? –

4

no quiero complicar el razonamiento sobre esto considerando ref parámetros y el boxeo. También sé que p = p.Offset(3, 4); expresa inmutabilidad mucho mejor que p.Offset(3, 4); hace. Pero la pregunta permanece - ¿no son los tipos de valor inmutables por definición?

Bueno, entonces realmente no estás operando en el mundo real, ¿o sí? En la práctica, la propensión de los tipos de valores a hacer copias de sí mismos mientras se mueven entre funciones se combina bien con la inmutabilidad, pero no son inmutables a menos que los conviertas en inmutables, ya que, como dijiste, puedes usar referencias a ellos solo como cualquier otra cosa

+0

Por supuesto, esta es una pregunta bastante teórica y el asunto con ref y boxeo, todavía no me di cuenta completamente. Tiendo a decir que ref no es problema porque obtienes una referencia a la variable, no al valor contenido. El boxeo parece un poco más difícil y todavía estoy pensando en eso. –

+0

Su argumento para ref no tiene sentido. Sí, obtiene una referencia, pero el valor que está modificando sigue siendo un tipo de valor. – jalf

+0

No entiendo muy bien de qué estás hablando con respecto a la referencia tampoco. Aquí está el objeto: he obtenido una referencia a él.Puedo cambiarlo, lo que cambia los valores asociados con ese mismo objeto en la memoria. ¿Cómo es esto "inmutable" en cualquier sentido de la palabra? Está actuando como cualquier tipo de referencia en este punto. – mquander

4

no son tipos de valores inmutables por definición?

n no lo son: si nos fijamos en la estructura System.Drawing.Point por ejemplo, tiene un regulador, así como un captador en su propiedad X.

Sin embargo, puede ser cierto que todos los tipos de valores deben definirse con con API inmutables.

+0

Sí, tiene el colocador, pero podría reescribir el punto.X = 42; como point = point.SetX (42) - la pregunta es si esto siempre es posible. En caso afirmativo, podría considerar que la estructura es inmutable (pero con una interfaz que no expresa esta inmutabilidad muy bien). –

+0

Si el propósito de un tipo es encapsular una colección fija de variables independientes (como las coordenadas de un punto), la realización óptima es una estructura con campos públicos expuestos (que se comportará como una colección fija de variables independientes). Uno puede lograr este tipo de comportamiento torpemente con una clase inmutable, y puede codificar una estructura de tal manera que sea tan difícil de usar como la clase inmutable, pero si el objetivo es encapsular un conjunto fijo de variables unidas al tipo de conducto, ¿por qué no utilizar un tipo de datos que se implementa y se comporta exactamente como se desea? – supercat

+0

@supercat El problema principal es structs con una propiedad set, que le permite hacer algo como 'point.X + = 3' que no hace lo que espera; mientras que una API que requiere que digas 'point.SetX (point.X + 3)' no es tan propenso a errores. – ChrisW

0

Los objetos/estructuras son inmutables cuando se pasan a una función de tal manera que los datos no se pueden cambiar, y la estructura devuelta es una estructura new.El ejemplo clásico es

String s = "abc";

s.toLower();

si la función toLower está escrito para que una nueva cadena se devuelve que sustituye a "s", es inmutable, pero si la función va letra por letra reemplazando el letra dentro de "s" y nunca declarar una "nueva cadena", es mutable.

5

Si toma su lógica lo suficiente, entonces todos los tipos son inmutables. Cuando modifica un tipo de referencia, puede argumentar que realmente está escribiendo un nuevo objeto en la misma dirección, en lugar de modificar algo.

O podría argumentar que todo es mutable, en cualquier idioma, porque de vez en cuando la memoria que se había utilizado previamente para una cosa, será sobrescrita por otra.

Con suficientes abstracciones e ignorando suficientes características de idioma, puede llegar a cualquier conclusión que desee.

Y eso no tiene sentido. Según la especificación .NET, los tipos de valores son mutables. Puedes modificarlo

int i = 0; 
Console.WriteLine(i); // will print 0, so here, i is 0 
++i; 
Console.WriteLine(i); // will print 1, so here, i is 1 

pero sigue siendo lo mismo i. La variable i solo se declara una vez. Todo lo que le sucede después de esta declaración es una modificación.

En algo así como un lenguaje funcional con variables inmutables, esto no sería legal. El ++ i no sería posible. Una vez que se ha declarado una variable, tiene un valor fijo.

En .NET, ese no es el caso, no hay nada que me impida modificar el i después de haber sido declarado.

Después de pensarlo un poco más, aquí hay otro ejemplo que podría ser mejor:

struct S { 
    public S(int i) { this.i = i == 43 ? 0 : i; } 
    private int i; 
    public void set(int i) { 
    Console.WriteLine("Hello World"); 
    this.i = i; 
    } 
} 

void Foo { 
    var s = new S(42); // Create an instance of S, internally storing the value 42 
    s.set(43); // What happens here? 
} 

En la última línea, de acuerdo con su lógica, podríamos decir que en realidad construye un nuevo objeto, y sobrescribir el viejo con ese valor. ¡Pero eso no es posible! Para construir un objeto nuevo, el compilador debe establecer la variable i en 42. ¡Pero es privada! Solo se puede acceder a través de un constructor definido por el usuario, que explícitamente no permite el valor 43 (configurándolo a 0 en su lugar), y luego a través de nuestro método set, que tiene un desagradable efecto secundario. El compilador no tiene forma de solo creando un nuevo objeto con los valores que le gustan. La única forma en que s.i se puede configurar en 43 es por modificando el objeto actual llamando al set(). El compilador no puede hacer eso, porque cambiaría el comportamiento del programa (se imprimiría en la consola)

Entonces, para que todas las estructuras sean inmutables, el compilador tendría que hacer trampas y romper las reglas de la consola. idioma. Y, por supuesto, si estamos dispuestos a romper las reglas, podemos probar cualquier cosa. Podría probar que todos los enteros son iguales también, o que definir una nueva clase hará que tu computadora se incendie. Mientras nos mantengamos dentro de las reglas del lenguaje, las estructuras son mutables.

+0

Quizás la mejor respuesta hasta ahora. Pero creo que estos son dos conceptos: mutabilidad de una variable y mutabilidad del valor de una variable. Pensando en ello ... +1 –

+0

Agregué otro ejemplo – jalf

+0

Lo he reescrito ... Modifiqué un poco su ejemplo: si le presentaba algo que no quería, deshaga esto, pero creo que realmente quería establezca el campo en 43. –

1

No, no lo son.Ejemplo:

Point p = new Point (3,4); 
Point p2 = p; 
p.moveTo (5,7); 

En este ejemplo moveTo() es un operación en el lugar. Cambia la estructura que se esconde detrás de la referencia p. Puede ver eso mirando el p2: su posición también habrá cambiado. Con estructuras inmutables, moveTo() tendría que devolver una nueva estructura:

p = p.moveTo (5,7); 

Ahora, Point es inmutable y cuando se crea una referencia a él en cualquier parte de su código, usted no recibirá sorpresas. Veamos i:

int i = 5; 
int j = i; 
i = 1; 

Esto es diferente. i no es inmutable, 5 es. Y la segunda asignación no copia una referencia a la estructura que contiene i pero copia el contenido de i. De modo que detrás de escena ocurre algo completamente diferente: se obtiene una copia completa de la variable en lugar de solo una copia de la dirección en la memoria (la referencia).

Un equivalente con objetos sería el constructor de copia:

Point p = new Point (3,4); 
Point p2 = new Point (p); 

Aquí, la estructura interna de p se copia en un nuevo objeto/estructura y p2 contendrá la referencia a la misma. Pero esta es una operación bastante costosa (a diferencia de la asignación de enteros anterior) que es la razón por la cual la mayoría de los lenguajes de programación hacen la distinción.

A medida que las computadoras se vuelven más potentes y obtienen más memoria, esta distinción desaparecerá porque causa una enorme cantidad de errores y problemas. En la próxima generación, solo habrá objetos inmutables, cualquier operación estará protegida por una transacción e incluso un int será un objeto completo. Al igual que la recolección de basura, será un gran paso adelante en la estabilidad del programa, causará mucho dolor en los primeros años, pero permitirá escribir software confiable. Hoy en día, las computadoras simplemente no son lo suficientemente rápidas para esto.

+0

Usted dijo: "Ahora, Point es inmutable, etc.", pero ese no es un buen ejemplo: Point is * not * inmutable. – ChrisW

+1

Está equivocado, p2 no será igual a p después de la llamada al método p.moveTo (5,7) si Point es un tipo de valor. –

+0

@Daniel: estoy en lo cierto ya que Point no es un tipo de valor en mi ejemplo. ("operación en el lugar") –

2

Creo que la confusión es que si tiene un tipo de referencia que debería actuar como un tipo de valor, es una buena idea hacerlo inmutable. Una de las principales diferencias entre los tipos de valores y los tipos de referencia es que un cambio realizado a través de un nombre en un tipo de referencia puede aparecer en el otro nombre. Esto no sucede con los tipos de valor:

public class foo 
{ 
    public int x; 
} 

public struct bar 
{ 
    public int x; 
} 


public class MyClass 
{ 
    public static void Main() 
    { 
     foo a = new foo(); 
     bar b = new bar(); 

     a.x = 1; 
     b.x = 1; 

     foo a2 = a; 
     bar b2 = b; 

     a.x = 2; 
     b.x = 2; 

     Console.WriteLine("a2.x == {0}", a2.x); 
     Console.WriteLine("b2.x == {0}", b2.x); 
    } 
} 

Produce:

a2.x == 2 
b2.x == 1 

Ahora, si usted tiene un tipo que le gustaría tener la semántica de valor, pero no quiere hacer realidad es un tipo de valor, tal vez porque el almacenamiento que requiere es demasiado o lo que sea, debe considerar que la inmutabilidad es parte del diseño. Con un tipo de referencia inmutable, cualquier cambio realizado en una referencia existente produce un nuevo objeto en lugar de cambiar el existente, de modo que obtienes el comportamiento del tipo de valor de que cualquier valor que tengas no se puede cambiar por otro nombre.

Por supuesto, la clase System.String es un excelente ejemplo de dicho comportamiento.

+0

Ese punto es claro: los tipos de referencia con tipo de valor semántica deben o, al menos, deben diseñarse como inmutables. De su declaración "[...] Esto no sucede con los tipos de valor: [...]" Concluyo que tiende a estar de acuerdo con mi conclusión: los tipos de valor son inmutables por definición porque no puede obtener una referencia a un valor , ¿derecho? –

+0

No, los tipos de valores no son inmutables por definición. En mi ejemplo anterior, la instrucción 'b.x = 2;' cambia 'b' - simplemente no cambia' b2'. Esa es una diferencia clave entre los tipos de valores y los tipos de ref. Supongo que podrías verlo como si 'b' tuviera un objeto completamente nuevo con un nuevo valor cuando se cambia, pero eso no es lo que está sucediendo y no veo nada útil al pensar en eso de esa manera. –

+0

Ahora entendiste mi punto. Actualmente estoy pensando en myStruct.DoStuff(); reescrito como myStruct = myStruct.DoStuff(); porque esto hace que la inmutabilidad de la estructura obviamente. Mi pregunta podría reformularse: ¿puede encontrar un ejemplo donde la transformación mencionada no se puede hacer o no funcionará? –

1

No, los tipos de valor son no inmutable por definición.

En primer lugar, debería haber hecho la pregunta "¿Los tipos de valores se comportan como tipos inmutables?" en lugar de preguntar si son inmutables, supongo que esto causó mucha confusión.

struct MutableStruct 
{ 
    private int state; 

    public MutableStruct(int state) { this.state = state; } 

    public void ChangeState() { this.state++; } 
} 

struct ImmutableStruct 
{ 
    private readonly int state; 

    public MutableStruct(int state) { this.state = state; } 

    public ImmutableStruct ChangeState() 
    { 
     return new ImmutableStruct(this.state + 1); 
    } 
} 

[Continuará ...]

2

El año pasado escribí un post acerca de los problemas que se pueden ejecutar en, al no hacer estructuras inmutable.

The full post can be read here

Este es un ejemplo de cómo las cosas pueden ir muy mal: muestra

//Struct declaration: 

struct MyStruct 
{ 
    public int Value = 0; 

    public void Update(int i) { Value = i; } 
} 

Código:

MyStruct[] list = new MyStruct[5]; 

for (int i=0;i<5;i++) 
    Console.Write(list[i].Value + " "); 
Console.WriteLine(); 

for (int i=0;i<5;i++) 
    list[i].Update(i+1); 

for (int i=0;i<5;i++) 
    Console.Write(list[i].Value + " "); 
Console.WriteLine(); 

La salida de este código es:

0 0 0 0 0 
1 2 3 4 5 

Ahora vamos a hacer lo mismo, pero sustituya la matriz de un genérico List<>:

List<MyStruct> list = new List<MyStruct>(new MyStruct[5]); 

for (int i=0;i<5;i++) 
    Console.Write(list[i].Value + " "); 
Console.WriteLine(); 

for (int i=0;i<5;i++) 
    list[i].Update(i+1); 

for (int i=0;i<5;i++) 
    Console.Write(list[i].Value + " "); 
Console.WriteLine(); 

La salida es:

0 0 0 0 0 
0 0 0 0 0 

La explicación es muy simple. No, no es un boxeo/unboxing ...

Al acceder a los elementos de una matriz, el tiempo de ejecución obtendrá los elementos de la matriz directamente, por lo que el método Update() funciona en el elemento de la matriz. Esto significa que las estructuras en el conjunto se actualizan.

En el segundo ejemplo, utilizamos un genérico List<>. ¿Qué sucede cuando accedemos a un elemento específico? Bueno, se llama a la propiedad del indexador, que es un método. Los tipos de valores siempre se copian cuando los devuelve un método, por lo que esto es exactamente lo que sucede: el método del indizador de la lista recupera la estructura de una matriz interna y la devuelve a la persona que llama. Debido a que se trata de un tipo de valor, se realizará una copia y se llamará al método Update() en la copia, que por supuesto no tiene ningún efecto en los elementos originales de la lista.

En otras palabras, siempre asegúrese de que sus estructuras sean inmutables, porque nunca está seguro de cuándo se realizará una copia. La mayoría de las veces es obvio, pero en algunos casos realmente puede sorprenderlo ...

+1

El problema no es que las estructuras mutables sean malas, sino que C# no tiene un medio para indicar qué métodos mutarán una estructura, por lo que puede prohibir su uso en contextos de solo lectura. En comparación con los objetos promiscuos, las estructuras mutables a menudo ofrecen una semántica muy superior. Si tengo una struct 'foo', y llamo bar1 (foo), puedo garantizar que 'bar' no cambiará ninguno de los campos de foo (si algunos de los campos contienen referencias de clase, es posible que los objetivos de esas referencias podrían cambiarse). Si llamo a bar2 (ref foo), bar2() puede cambiar foo, pero ... – supercat

+1

... cualquier cambio de este tipo que ocurra ocurrirá antes de que regrese bar(). Por el contrario, si tengo un objeto de clase 'zoo' y llamo bar3 (zoo), es posible que bar3() pueda mutar el zoo inmediatamente, o podría almacenar una referencia al zoo en alguna parte que provocará que otro hilo lo mute en algún tiempo arbitrario futuro. Eso parece mucho más malvado que cualquier problema que tengan las estructuras mutables. Para estar seguro, el soporte de .net para estructuras mutables tiene algunos caprichos extraños, pero esas son fallas de .net, no el concepto de estructuras mutables. – supercat

1

Para definir si un tipo es mutable o inmutable, se debe definir a qué se refiere ese "tipo". Cuando se declara una ubicación de almacenamiento de tipo de referencia, la declaración simplemente asigna espacio para mantener una referencia a un objeto almacenado en otro lugar; la declaración no crea el objeto real en cuestión. No obstante, en la mayoría de los contextos donde se habla de tipos de referencia particulares, uno no estará hablando de una ubicación de almacenamiento que contiene una referencia, sino más bien el objeto identificado por esa referencia. El hecho de que uno pueda escribir en un lugar de almacenamiento con una referencia a un objeto implica que el objeto en sí no puede ser modificado.

Por el contrario, cuando se declara una ubicación de almacenamiento de tipo de valor, el sistema asignará dentro de esa ubicación de almacenamiento ubicaciones de almacenamiento anidadas para cada campo público o privado mantenido por ese tipo de valor. Todo sobre el tipo de valor se mantiene en esa ubicación de almacenamiento. Si se define una variable foo de tipo Point y sus dos campos, X y Y, mantenga 3 y 6 respectivamente. Si se define la "instancia" de Point en foo como el par de campos, esa instancia será mutable si y solo si foo es mutable. Si se define una instancia de Point como valores en esos campos (por ejemplo, "3,6"), dicha instancia es por definición inmutable, ya que el cambio de uno de esos campos provocaría que Point tuviera una instancia diferente.

Creo que es más útil pensar en un tipo de valor "instancia" como los campos, en lugar de los valores que contienen. Según esa definición, cualquier tipo de valor almacenado en una ubicación de almacenamiento mutable, y para el que exista cualquier valor no predeterminado, será siempre mutable, independientemente de cómo se declare. Una instrucción MyPoint = new Point(5,8) construye una nueva instancia de Point, con los campos X=5 y Y=8, y luego muta MyPoint reemplazando los valores en sus campos con los del recién creado Point. Incluso si una estructura no proporciona ninguna forma de modificar ninguno de sus campos fuera de su constructor, no hay forma de que un tipo de estructura pueda proteger una instancia para que no se sobrescriban todos sus campos con el contenido de otra instancia.

Incidentalmente, un simple ejemplo en el que una estructura mutable puede lograr la semántica no alcanzables a través de otros medios: Suponiendo myPoints[] es una matriz de un solo elemento que es accesible a múltiples hilos, se han ejecutar veinte hilos simultáneamente el código:

Threading.Interlocked.Increment(myPoints[0].X); 

Si myPoints[0].X comienza igual que cero y veinte hilos realizan el código anterior, ya sea simultáneamente o no, myPoints[0].X equivaldrá a veinte. Si uno fuera a tratar de imitar el código anterior con:

myPoints[0] = new Point(myPoints[0].X + 1, myPoints[0].Y); 

Entonces, si alguno hilo leer myPoints[0].X entre el momento en otro hilo leyó y escribió de nuevo el valor revisado, los resultados del incremento se perderían (con la consecuencia de que myPoints[0].X podría terminar arbitrariamente con cualquier valor entre 1 y 20.

Cuestiones relacionadas