Pregunta

He leído que structs debe ser inmutable - no son, por definición?

¿Considera usted int para ser inmutable?

int i = 0;
i = i + 123;

Parece estar bien - tenemos un nuevo int y lo asignará a i.Lo que acerca de esto?

i++;

Bien, podemos pensar que es como un acceso directo.

i = i + 1;

¿Qué acerca de la struct Point?

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

Esto realmente mutar el punto de (1, 2)?No debemos pensar en él como un acceso directo para el siguiente Point.Offset() la devolución de un nuevo punto?

p = p.Offset(3, 4);

El fondo de este pensamiento es esta: ¿cómo puede un tipo de valor sin identidad mutable?Usted tiene que mirar al menos dos veces para determinar si ha cambiado.Pero ¿cómo se puede hacer esto sin una identidad?

No quiero complicar el razonamiento acerca de este considerando ref los parámetros y el boxeo.También soy consciente de que p = p.Offset(3, 4); expresa la inmutabilidad mucho mejor que p.Offset(3, 4); no.Pero la pregunta sigue siendo - no son tipos de valor inmutable por definición?

ACTUALIZACIÓN

Creo que hay al menos dos conceptos implicados - la mutabilidad de una variable o campo y de 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 a los campos mutables uno y una inmutable.Porque un tipo de valor de campo contiene el valor entero, un tipo de valor almacenado en una inmutable campo debe ser inmutable, demasiado.Todavía estoy muy sorprendido por el resultado - que no me exspect el campo readonly a permanecer sin modificar.

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


La respuesta parece no ser que la recta hacia adelante, así 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 ...
}

Puede usted dar una versión completa de Foo y un ejemplo de uso - que puede incluir ref parámetros y boxeo - de modo que no es posible reescribir todas las ocurrencias de

foo.DoStuff(whatEverArgumentsYouLike);

con

foo = foo.DoStuff(whatEverArgumentsYouLike);
¿Fue útil?

Solución

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

Respuesta corta:No, los tipos de valor que no son inmutables, por definición. Ambas estructuras y las clases pueden ser mutable o inmutable. Las cuatro combinaciones son posibles.Si una estructura o clase no tiene readonly pública campos, propiedades públicas con incubadoras, o los métodos que se establece en los campos privados, es mutable, ya que puede cambiar su estado sin necesidad de crear una nueva instancia de ese tipo.


Respuesta larga:Primero de todo, la cuestión de la inmutabilidad sólo se aplica a estructuras o clases con los campos o propiedades.La mayoría de los tipos básicos (números, cadenas, null) son inherentemente inmutable porque no hay nada (de campo/propiedad) a cambio de ellos.Un 5 a 5 a 5.Cualquier operación en el 5 sólo devuelve otro valor inmutable.

Usted puede crear mutable estructuras tales como System.Drawing.Point.Ambos X y Y tienen incubadoras que modificar la estructura de campos:

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 valor que 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 fue modificado debido a que SetX() modificado p2 que es un copia de p1.Esto sucede porque p1 es un tipo de valor, no , porque es inmutable (no lo es).

Por qué debe los tipos de valor inmutable?Un montón de razones...Ver esta pregunta.Sobre todo es porque los tipos de valor que conducen a todo tipo de errores obvios.En el ejemplo anterior, el programador podría haber esperado p1 para ser (5, 0) después de llamar a SetX().O imaginar la clasificación por un valor que puede cambiar más adelante.Luego de su colección ordenada ya no se ordenan como se esperaba.Lo mismo va para los diccionarios y hash.El Fabuloso Eric Lippert (blog) ha escrito un toda la serie acerca de la inmutabilidad de y por qué él cree que es el futuro de C#. He aquí uno de sus ejemplos que le permite "modificar" una lectura única variable.


ACTUALIZACIÓN:su ejemplo con:

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

es exactamente lo que Lippert se refiere en su post acerca de la modificación de variables de solo lectura. Offset(3,4) modificada realmente un Point, pero fue un copia de readOnlyPoint, y nunca fue asignado a nada, así que se pierde.

Y que es por eso que mutable tipos de valor están mal:Ellos te permiten creo se modifica algo, cuando a veces son en realidad la modificación de una copia, lo que conduce a errores inesperados.Si Point era inmutable, Offset() tendría que regresar de un nuevo Point, y no han sido capaces de asignar a readOnlyPoint.Y luego vas "Ah, claro, es de lectura-sólo por una razón.¿Por qué estoy tratando de cambiar eso?Bueno el compilador me detuvo ahora."


ACTUALIZACIÓN:Acerca de su reformularse de la solicitud de...Yo creo saber lo que estás diciendo.En una manera, usted puede "pensar" de estructuras como internamente inmutable, que la modificación de una estructura es la misma que se sustituye con una copia modificada.Incluso podría ser lo que el CLR no internamente en la memoria, para todos los que yo conozco.(Que es como la memoria flash funciona.Usted no puede editar sólo unos pocos bytes, usted necesita leer un bloque entero de kilobytes en la memoria, modificar los pocos que desea, y escribir todo el bloque de nuevo.) Sin embargo, incluso si se tratara de "internamente inmutable", que es un detalle de implementación y para nosotros los desarrolladores como a los usuarios de estructuras (su interfaz o API, si se quiere), que puede ser cambiado.No podemos ignorar ese hecho y "pensar en ellos como inmutable".

En un comentario que dice "no se puede tener una referencia para el valor del campo o de una variable".Usted está asumiendo que cada estructura variable tiene una copia diferente, de tal manera que la modificación de una copia no afectará a las demás.Que 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

Las líneas #1 y #2 no tienen los mismos resultados...Por qué?Porque foo y otherFoo consulte la misma caja instancia de Foo.Lo que se cambia en foo en la línea #1 se refleja en otherFoo.La línea #2 reemplaza foo con un nuevo valor y no hace nada para otherFoo (suponiendo que DoStuff() devuelve un nuevo IFoo instancia y no modificar foo en sí).

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

La modificación de foo1 no afecta foo2 o foo3.La modificación de foo2 se reflejan en foo3, pero no en foo1.La modificación de foo3 se reflejan en foo2 pero no en foo1.

Confuso?Palo para inmutable tipos de valor y eliminar la necesidad de la modificación de cualquiera de ellos.


ACTUALIZACIÓN:se corrigió un error en el primer ejemplo de código

Otros consejos

mutabilidad y la de valor tipos son dos cosas separadas.

Definición de un tipo como un tipo de valor, indica que el tiempo de ejecución copiará los valores en lugar de una referencia al tiempo de ejecución. Mutabilidad, por el contrario, depende de la aplicación, y cada clase puede implementar, ya que quiere.

Puede escribir estructuras que son mutables, pero es la mejor práctica para que los tipos de valor inmutable.

Por ejemplo DateTime siempre crea nuevas instancias al hacer cualquier operación. Punto es mutable y se puede cambiar.

Para responder a su pregunta: No, ellos no son inmutables, por definición, que depende del caso si deben ser mutable o no. Por ejemplo, si deben servir como claves de diccionario, deben ser inmutables.

Si usted toma su lógica lo suficiente, entonces todos tipos son inmutables. Cuando se modifica un tipo de referencia, se podría argumentar que en realidad está escribiendo un nuevo objeto a la misma dirección, en lugar de modificar nada.

O se podría argumentar que todo es mutable, en cualquier idioma, porque de vez en memoria que había sido utilizado anteriormente por un lado, será reemplazado por otro.

Con suficientes abstracciones, y haciendo caso omiso suficientes características del lenguaje, se puede llegar a ninguna conclusión que quiera.

Y que no ve el punto. De acuerdo con las especificaciones .NET, los tipos de valor son mutables. Puede 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 el mismo i. El i variable sólo se declara una vez. Cualquier cosa que sucede con él 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. La ++ i no sería posible. Una vez que una variable ha sido declarada, que tiene un valor fijo.

En .NET, que no es el caso, no hay nada que me impida modificar el set después de que haya sido declarada.

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 la antigua con ese valor. Pero eso no es posible! Para construir un nuevo objeto, el compilador tiene que establecer la variable de s.i a 42. Pero es privado! Sólo es accesible a través de un constructor definido por el usuario, lo que no permite explícitamente el valor 43 (ponerlo a 0 en su lugar), y luego a través de nuestra set() método, que tiene un desagradable efecto secundario. El compilador no tiene forma de solo crear un nuevo objeto con los valores que le gustan. La única manera en la que <=> se puede ajustar a 43 es por modificador el objeto actual llamando <=>. El compilador no puede hacer eso, porque sería cambiar el comportamiento del programa (que imprimiría a la consola)

Así que para todas las estructuras que son inmutables, el compilador tendría que engañar y romper las reglas del lenguaje. Y, por supuesto, si estamos dispuestos a romper las reglas, podemos probar nada. Podría probar que todos los enteros son también iguales, o que la definición de una nueva clase a hacer que el equipo se prenda fuego. Mientras nos mantenemos dentro de las reglas del lenguaje, las estructuras son mutables.

  

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

Bueno, entonces usted no está realmente operativo en el mundo real, ¿verdad? En la práctica, la propensión de los tipos de valor para hacer copias de sí mismos a medida que se mueven entre las funciones se articule con la inmutabilidad, pero en realidad no son inmutables, a menos que los hacen inmutable, ya que, como usted ha señalado, puede utilizar las referencias a ellos simplemente al igual que cualquier otra cosa.

  

No son los tipos de valor inmutable por definición?

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

Sin embargo, puede ser cierto que todos los tipos de valor debe definirse con las API inmutables.

Creo que la confusión es que si usted tiene un tipo de referencia que debe actuar como un tipo de valor es una buena idea para que sea inmutable. Una de las principales diferencias entre los tipos de valor y 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 un tipo de valor - tal vez debido a que el almacenamiento de lo que requiere es demasiado o lo que sea, se debe considerar que la inmutabilidad es parte del diseño. Con un tipo de ref inmutable, cualquier cambio realizado en una referencia existente produce un nuevo objeto en lugar de cambio de la existente, por lo que se obtiene el comportamiento del tipo de valor que cualquier valor que está sosteniendo no se puede cambiar a través de algún otro nombre.

Por supuesto, la clase System.String es un buen ejemplo de este tipo de comportamiento.

El año pasado escribí un post acerca de los problemas que puede encontrarse con, al no hacer estructuras  inmutable.

El post completo se puede leer aquí

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

//Struct declaration:

struct MyStruct
{
  public int Value = 0;

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

Ejemplo de 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 el boxeo / unboxing ...

Cuando se accede 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 propio elemento de la matriz. Esto significa que las propias estructuras de la matriz se actualizan.

En el segundo ejemplo, se utilizó una <=> genérico. ¿Qué pasa cuando se accede a un elemento específico? Así, la propiedad paso a paso se llama, que es un método. Los tipos de valor siempre se copian cuando son devueltos por un método, así que esto es exactamente lo que sucede: el método paso a paso de la lista recupera la estructura de una matriz interna y lo devuelve a la persona que llama. Debido a que se trata de un tipo de valor, se hará una copia, y el método Update () será llamado en la copia, que por supuesto no tiene efecto sobre los elementos originales de la lista.

En otras palabras, siempre asegúrese de que sus estructuras son inmutables, porque nunca se está seguro cuando se hace una copia. La mayor parte del tiempo, es obvio, pero en algunos casos lo que realmente le puede sorprender ...

No, no lo son. Ejemplo:

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

En este ejemplo moveTo() es un in situ operación. Cambia la estructura que se esconde detrás de la referencia p. Se puede ver que por la mirada en p2: Su posición también han cambiado. Con estructuras inmutables, Point tendría que devolver una nueva estructura:

p = p.moveTo (5,7);

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

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

Esto es diferente. int no es inmutable <=>, es. Y la segunda asignación no copia una referencia a la estructura que contiene <=> pero copia el contenido de <=>. Así que detrás de las escenas, algo completamente diferente sucede: Usted recibe una copia completa de la variable en lugar de solamente 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 <=> se copia en un nuevo objeto / estructura y <=> contendrá la referencia a él. Pero esta es una operación bastante caro (a diferencia de la asignación de número entero más arriba), que es la razón por la mayoría de los lenguajes de programación hacen la distinción.

Mientras que las computadoras se vuelven más potentes y obtener más memoria, esta distinción va a desaparecer, ya que causa una enorme cantidad de errores y problemas. En la siguiente generación, sólo habrá objetos inmutables, cualquier operación será protegida por una transacción e incluso un <=> será un objeto completo soplado. Al igual que la recolección de basura, que será un gran paso adelante en la estabilidad del programa, causar muchos dolores de cabeza en los primeros años pero permitirá a escribir software fiable. Hoy en día, los ordenadores no son lo suficientemente rápido para esto.

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

En primer lugar, debería haber pedido mejor la pregunta "¿los tipos de valor se comportan como tipos inmutables?" en lugar de preguntar si son inmutables - Asumo 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á ...]

Para definir si un tipo es mutable o inmutable, uno debe definir qué es ese "tipo" se refiere.Cuando una ubicación de almacenamiento de tipo de referencia que se declara, la declaración simplemente asigna espacio para almacenar una referencia a un objeto almacenado en otro lugar;la declaración no crea el objeto en cuestión.No obstante, en la mayoría de los contextos donde se habla sobre determinados tipos de referencia, uno no va a estar hablando de una ubicación de almacenamiento que contiene una referencia, pero en lugar el objeto identificado por la referencia.El hecho de que uno puede escribir en una ubicación de almacenamiento de la celebración de una referencia a un objeto implica de ninguna manera que el objeto en sí es mutable.

Por el contrario, cuando una ubicación de almacenamiento de tipo de valor declarado, el sistema asignará dentro de esa ubicación de almacenamiento anidada ubicaciones de almacenamiento para cada públicas o privadas campo que posea en el tipo de valor.Todo sobre el tipo de valor que se celebró en que la ubicación de almacenamiento.Si se define una variable foo de tipo Point y sus dos campos, X y Y, mantenga pulsado 3 y 6, respectivamente.Si uno define la "instancia" de Point en foo como el par de campos, esa instancia será mutable si y sólo si foo es mutable.Si uno define una instancia de Point como el los valores de a cabo en los campos (p. ej."3,6"), a continuación, un ejemplo es, por definición, inmutable, ya que el cambio de uno de los campos de la causa Point para mantener 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 poseen.Por definición, cualquier tipo de valor almacenado en una variable ubicación de almacenamiento, y para que cualquier valor no predeterminado existe, se siempre ser mutable, independientemente de cómo se le declaró.Una declaración MyPoint = new Point(5,8) crea una nueva instancia de Point, con los campos de X=5 y Y=8, y , a continuación, muta MyPoint reemplazando los valores en sus campos con los de la recién creada Point.Incluso si una estructura no proporciona ninguna forma de modificar cualquiera de sus campos fuera de su constructor, no hay ninguna forma una estructura tipo puede proteger a una instancia de tener todos sus campos se sobrescribe con el contenido de otra instancia.

Por cierto, un ejemplo simple donde mutables struct puede lograr la semántica no se puede lograr a través de otros medios:Suponiendo myPoints[] es de un solo elemento de la matriz, que es accesible a varios subprocesos, tiene veinte hilos simultáneamente ejecutar el código:

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

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

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

entonces, si alguno hilo de leer myPoints[0].X entre el tiempo en otro hilo lo 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 arbitrariamente acabar con cualquier valor entre 1 y 20.

Objetos / Las 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 el toLower función se escribe por lo que una nueva cadena se devuelve que sustituye a "s", es inmutable, pero si la función va letra por letra sustitución de la letra dentro "s" y nunca se declara una "nueva cadena" , es mutable.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top