2010-10-04 7 views
13

En C#, si usted tiene un struct así:C#: ¿Por qué las mutaciones en las estructuras de solo lectura no se rompen?

struct Counter 
{ 
    private int _count; 

    public int Value 
    { 
     get { return _count; } 
    } 

    public int Increment() 
    { 
     return ++_count; 
    } 
} 

Y usted tiene un programa de este modo:

static readonly Counter counter = new Counter(); 

static void Main() 
{ 
    // print the new value from the increment function 
    Console.WriteLine(counter.Increment()); 
    // print off the value stored in the item 
    Console.WriteLine(counter.Value); 
} 

La salida del programa será:

1 
0 

Esto parece completamente incorrecto Yo esperaría que la salida fuera dos 1s (como lo es si Counter es un class o si struct Counter : ICounter y es un ICounter) o es un error de compilación. Me doy cuenta de que detectar esto en el tiempo de compilación es un asunto bastante difícil, pero este comportamiento parece violar la lógica.

¿Hay alguna razón para este comportamiento más allá de la dificultad de implementación?

+10

Ver Eric Lippert ["Mutating Readonly Structs"] (http://blogs.msdn.com/b/ericlippert/archive/2008/05/14/mutating-readonly-structs.aspx) –

+0

* ¿Por qué? esperar dos 1s? Dijiste que querías que fuera de solo lectura, ¿por qué quieres que cambie? –

Respuesta

8

structs son tipos de valor y, por lo tanto, tienen un tipo de valor sematics. Esto significa que cada vez que accede a la estructura, básicamente trabaja con una copia del valor de la estructura.

En su muestra no cambia el original struct sino solo una copia temporal del mismo.

ver aquí para más explicaciones:

Why are mutable structs evil

+4

Pero si quita el 'readonly', se produce el resultado esperado de dos 1s. Entonces claramente no hace una copia en esa instancia. ¿Significa esto que todas las funciones llamadas a las estructuras de solo lectura crean copias de la estructura (ya que C# no tiene ningún concepto de función de mutación)? –

+4

Sí, ese es el caso de acuerdo con la sección 7.5.4 de la especificación del lenguaje C#. Vea la publicación de Eric Lippert sobre el tema para obtener más detalles. –

+1

Una debilidad de diseño desafortunada en .net es que no hay ningún medio por el cual los métodos struct puedan indicar si van a modificar 'this'. Ya que sería molesto si no se pudieran usar métodos o propiedades en las estructuras de solo lectura, y también sería molesto si no se respetaran los modificadores 'readonly' en las estructuras, C# y vb.net en lugar de" compromiso ", por tener invocaciones de método en estructuras de solo lectura hace una copia de la estructura antes de invocar el método. Tenga en cuenta que si uno expone los campos de estructura directamente, el compilador podrá distinguir los accesos de lectura de las escrituras, y ... – supercat

3

En .NET, un método de instancia estructura es semánticamente equivalente a un método estático con una estructura ref un parámetro adicional de la estructura tipo. Por lo tanto, teniendo en cuenta las declaraciones:

struct Blah { 
    public int value; 
    public void Add(int Amount) { value += Amount; } 
    public static void Add(ref Blah it; int Amount; it.value += Amount;} 
} 

El método llama:

someBlah.Add(5); 
Blah.Add(ref someBlah, 5); 

son semánticamente equivalentes, excepto por una diferencia: solamente se permitirá a esta última llamada si someBlah es una ubicación de almacenamiento mutable (variable, campo, etc.) y no si se trata de una ubicación de almacenamiento de solo lectura, o un valor temporal (resultado de leer una propiedad, etc.).

Esto enfrentó a los diseñadores de lenguajes .NET con un problema: no permitir el uso de cualquier función miembro en las estructuras de solo lectura sería molesto, pero no querían permitir que las funciones miembro escribieran a las variables de solo lectura. Decidieron "punt", y lo hacen de modo que llamar a un método de instancia en una estructura de solo lectura hará una copia de la estructura, invocar la función sobre eso y luego descartarla. Esto tiene el efecto de ralentizar llamadas a métodos de instancia que no escriben la estructura subyacente, y hacer que un intento de utilizar un método que actualice la estructura subyacente en una estructura de solo lectura produzca semántica rota diferente de lo que se lograría si se pasa la estructura directamente. Tenga en cuenta que el tiempo extra tomado por la copia casi nunca arrojará la semántica correcta en casos que no hubieran sido correctos sin la copia.

Uno de mis principales problemas en .net es que todavía hay (al menos 4.0, y probablemente 4.5) todavía ningún atributo a través del cual una función de miembro de estructura puede indicar si modifica this.La gente discute sobre cómo las estructuras deberían ser inmutables, en lugar de proporcionar las herramientas para permitir que las estructuras ofrezcan métodos de mutación seguros. Esto, a pesar del hecho de que las llamadas estructuras "inmutables" son una mentira. Todos los tipos de valores no triviales en ubicaciones de almacenamiento mutables son mutables, como todos los tipos de valores encuadrados. Hacer una estructura "inmutable" puede obligar a uno a reescribir una estructura completa cuando uno solo quiere cambiar un campo, pero desde struct1 = struct2 muta struct1 copiando todos los campos públicos y privados de struct2, y no hay nada que sea la definición de tipo para la estructura puede hacer para evitar que (excepto que no tenga ningún campo) no haga nada para evitar una mutación inesperada de los miembros de la estructura. Además, debido a problemas de enhebrado, las estructuras tienen una capacidad muy limitada para imponer cualquier tipo de relación invariable entre sus campos. En mi humilde opinión, generalmente sería mejor para una estructura permitir el acceso arbitrario al campo, dejando en claro que cualquier código que reciba una estructura debe verificar si sus campos cumplen con todas las condiciones requeridas, en lugar de tratar de evitar la formación de estructuras que no cumplan con las condiciones.

+0

* '" struct1 = struct2' muta estructura struct1 copiando "* - eso es cierto, pero ¿hay algún problema práctico con eso (excepto que generalmente no es atómico)? Tanto 'struct1' como' struct2' son variables o campos rellenos con datos reales, por lo que copiar es lo que debería esperarse en este caso, ya que así es como funciona la asignación de estructuras en C y C++ de forma predeterminada. A menos que 'struct1' sea un campo' readonly', en cuyo caso fallará en tiempo de compilación. – Groo

+0

De todos modos, +1 para obtener información útil, pero podría reformular ligeramente la parte donde dice que * llamar a un método de instancia en una estructura de solo lectura hará una copia de la estructura *, porque es que * accediendo * a 'solo 'struct field crea una copia de esa estructura. Si primero lee el valor en una variable local, puede mutarlo (siempre que sus campos internos sean mutables) y .NET operará directamente en la estructura basada en la pila ("detalles de implementación", lo sé). Cuando un campo no es 'readonly', los métodos pueden operar directamente en el campo sin necesidad de copiarlo. – Groo

+0

@Groo: no hay muchos casos de esquina donde importa que 'struct1 = struct2' mute la estructura * existente * sobrescribiendo todos sus campos, pero la única manera en que uno puede comprender correctamente esos casos es entendiendo qué es en realidad sucediendo. Con respecto a su segundo punto, no estoy del todo claro de lo que está sugiriendo; la frase "estructura de solo lectura" se refiere tanto a las variables que tienen un calificador 'readonly', como a las ubicaciones de almacenamiento temporal, como valores de retorno de función o propiedad; ¿Debo señalar que los valores de retorno de las propiedades de lectura/escritura son de solo lectura? – supercat

Cuestiones relacionadas