2012-04-10 9 views
5

Usualmente elijo entre struct y clase no por problemas de memoria sino por semántica del tipo. Algunos de mis tipos de valores tienen una huella de memoria bastante grande, a veces demasiado grande para copiar estos datos todo el tiempo. Entonces me pregunto si es una buena idea pasar objetos de valor inmutables siempre por referencia? Como los objetos son inmutables, no pueden ser modificados por métodos que los acepten por referencia. ¿Hay otros problemas al pasar por referencia?Pasar tipos de valores inmutables por referencia de forma predeterminada

+4

tal vez necesites volver a pensar tus razones para decidir entre estructuras y clases. –

Respuesta

14

Algunos de mis tipos de valores tienen gran capacidad de memoria muy

Eso sugiere que no deben ser los tipos de valor, desde el punto de vista de la implementación. De "Directrices de diseño para el Desarrollo de bibliotecas de clases", sección "Choosing Between Classes And Structures":

no definen una estructura a menos que el tipo tiene todas las siguientes características:

  • Es lógicamente representa un único valor, similar a la primitiva tipos (entero, doble, etc.).
  • Tiene un tamaño de instancia inferior a 16 bytes.
  • Es inmutable.
  • No tendrá que estar en la caja con frecuencia.

Parece que usted debe crear inmutable referencia tipos de cambio. En muchos sentidos terminan "sintiendo" como objetos de valor de todos modos (piense en cuerdas) pero no tendrá que preocuparse por la eficacia de pasarlos.

"inmutabilidad" para este tipo de valor es un concepto poco de líquido - y ciertamente no quiere decir que el uso de ref es segura:

// int is immutable, right? 
int x = 5; 
Foo(ref x); 
Console.WriteLine(x); // Eek, prints 6... 
... 
void Foo(ref int y) 
{ 
    y = 6; 
} 

No estamos cambiando una parte del valor - estamos reemplazando todo el valor de x con un valor completamente diferente.

inmutabilidad es algo más fácil que pensar cuando se trata de hacer referencia a tipos - aunque incluso entonces puede tener un objeto que en sí mismo no va a cambiar, pero puede referirse a objetos mutables ...

+0

"Tiene un tamaño de instancia inferior a 16 bytes". ¿Realmente escribes una 'clase Vector3d', porque tiene 24 bytes? – hansmaad

+2

@hansmaad Es solo una guía. No se pueden cubrir todos los casos, pero son cosas/reglas que se deben tener en cuenta al tomar la decisión. – vcsjones

+3

@hansmaad: si es probable que el tipo * pase por valor * mucho, entonces * sí *, debería hacerlo. Si es poco probable que el tipo de valor pase por valor, si acaba de crear uno y lo usa localmente sin pasarlo a otro método, continúe y amplíelo si lo desea. –

3

Así que me pregunto si es una buena idea pasar objetos de valor inmutables siempre por referencia? Como los objetos son inmutables, no pueden ser modificados por métodos que los acepten por referencia. ¿Hay otros problemas al pasar por referencia?

No está claro exactamente a qué te refieres. Suponiendo que quiere decir pasarlo como un parámetro ref o out, entonces el método podría simplemente asignar una nueva instancia a la ubicación de almacenamiento. Esto modificaría lo que ve la persona que llama porque la ubicación de almacenamiento en el destinatario es un alias para la ubicación de almacenamiento aprobada por la persona que llama.

Si tiene problemas de memoria debido a la copia de instancias de struct, debe considerar hacer un tipo de referencia inmutable, muy similar a string.

11

respuesta de Jon es correcta, por supuesto; Le agregaría esto: los tipos de valores son ya pasados ​​por referencia cuando llama a un método en el tipo de valor. Por ejemplo:

struct S 
{ 
    int x; 
    public S(int x) { this.x = x; } 
    public void M() { Console.WriteLine(this.x); } 
} 

Método M() es lógicamente lo mismo que:

public static void M(ref S _this) { Console.WriteLine(_this.x); } 

Cada vez que se llama a un método de instancia en una estructura, se pasa una referencia a la variable que fue el receptor de la llamada.

¿Qué sucede si el receptor no es una variable? Luego, , el valor se copia en una variable temporal que se utiliza como receptor. Y si el valor es grande, ¡esa es una copia potencialmente cara!

Los tipos de valores se copian por valor; es por eso que se llaman tipos de valor. A menos que esté planeando ser extremadamente cuidadoso para encontrar todas las posibles copias costosas y eliminarlas, seguiría los consejos de la guía de diseño de marco: mantenga las estructuras por debajo de 16 bytes y páselos por valor.

También quisiera enfatizar que Jon tiene razón: pasar una estructura por ref significa pasar una referencia a una variable, y las variables pueden cambiar. Es por eso que se llaman "variables". No hay "const ref" en C# como lo hay en C++; incluso si el tipo de valor en sí mismo parece ser "inmutable", eso no significa que la variable que lo contiene sea inmutable. Se puede ver un ejemplo extremo de que en este ejemplo artificioso pero educativa:

struct S 
{ 
    readonly int x; 
    public S(int x) { this.x = x; } 
    public void M(ref S s) 
    { 
     Console.WriteLine(this.x); 
     s = new S(this.x + 1); 
     Console.WriteLine(this.x); 
    } 
} 

¿Es posible que M para escribir dos números diferentes? Pensarías ingenuamente que la estructura es inmutable y, por lo tanto, x no puede cambiar. Sin embargo, tanto s y este son las variables y las variables pueden cambiar:

S q = new S(1); 
q.M(ref q); 

que imprime 1, 2, porque this y s son dos referencias a q, y nada impide que q cambien ; no es de solo lectura.

En resumen: si tuviera muchos datos que quisiera transmitir y tengo garantías sólidas de que era inmutable, usaría una clase, no una estructura.Solo use una estructura en ese escenario si tiene un problema de rendimiento demostrado que en realidad se resuelve convirtiéndolo en una estructura, teniendo en cuenta que las estructuras grandes son potencialmente muy costosas de copiar.

+0

Las llamadas estructuras inmutables pueden mostrarse mutables incluso cuando nada dentro de su código coopera en dicha mutación. Por ejemplo, aunque una ubicación de almacenamiento de 'KeyValuePair ' puede escribirse atómicamente (ya que toma exactamente 32 bits), tener un hilo 'ToString()' en dicha ubicación mientras otro hilo actualiza el valor puede causar 'ToString 'para devolver el antiguo' Key.ToString() 'concatenado con el nuevo' Value.ToString() '. – supercat

Cuestiones relacionadas