objeto example estructuras estructura entre ejemplo diferencia datos clase c# struct pass-by-value

example - objeto c#



¿Las estructuras son ''pasadas por valor''? (9)

Cada ubicación de almacenamiento de un tipo de estructura contiene todos los campos, privados y públicos, de esa estructura. Pasar un parámetro de un tipo de estructura implica asignar espacio para esa estructura en la pila y copiar todos los campos de la estructura a la pila.

Con respecto al trabajo con estructuras almacenadas dentro de una colección, el uso de estructuras mutables con las colecciones existentes generalmente requiere leer una estructura en una copia local, mutar esa copia y almacenarla de nuevo. Suponiendo que MyList es una List<Point> , y uno quiere agregar alguna variable local z a MyList[3].X :

Point temp = MyList[3]; temp.X += Z; MyList[3] = temp;

Esto es levemente molesto, pero a menudo es más limpio, más seguro y más eficiente que usar estructuras inmutables, mucho más eficiente que los objetos de clase inmutables, y mucho más seguro que usar objetos de clase mutables. Realmente me gustaría ver el soporte del compilador para una mejor manera de que las colecciones expongan elementos de tipo de valor. Hay formas de escribir código eficiente para manejar dicha exposición con buena semántica (por ejemplo, un objeto de colección podría reaccionar cuando los elementos se actualizaron, sin requerir que esos elementos tengan ningún vínculo con la colección) pero el código se lee de manera horrible. Agregar soporte de compilador de manera similar a los cierres permitirá que el código eficiente también sea legible.

Tenga en cuenta que, al contrario de lo que afirman algunas personas, una estructura es fundamentalmente diferente de un objeto de clase, pero para cada tipo de estructura hay un tipo correspondiente, a veces denominado "estructura encuadrada", que deriva de Object (consulte la CLI (Especificación de Common Language Infrastructure) , secciones 8.2.4 y 8.9.7). Aunque el compilador convertirá implícitamente cualquier estructura en su tipo encuadrado correspondiente cuando sea necesario para pasarla al código que espera una referencia a un objeto de clase, permitirá que las referencias a estructuras encuadradas tengan sus contenidos copiados en estructuras reales, y algunas veces permitirá código para trabajar directamente con estructuras encuadradas, las estructuras encuadradas se comportan como objetos de clase, porque eso es lo que son.

Recientemente traté de crear una propiedad para un campo Vector2 , solo para darme cuenta de que no funciona como estaba previsto.

public Vector2 Position { get; set; }

esto me impide cambiar los valores de sus miembros ( X e Y )

Buscando información sobre esto, leí que crear una propiedad en una estructura Vector2 devuelve solo una copia del objeto original y no una referencia.

Como desarrollador de Java, esto me confunde.

¿Cuándo pasan los objetos en C # por valor y cuándo se pasan por referencia?
¿Se pasan todos los objetos struct por valor?


El problema es que el getter devuelve una copia de Vector2 . Si cambias las coordenadas como esta

obj.Position.X = x; obj.Position.Y = y;

Solo cambias las coordenadas de esta copia efímera.

Haz esto en cambio

obj.Position = new Vector2(x, y);

Esto no tiene nada que ver con el valor o por referencia. Value2 es un tipo de valor y get devuelve este valor. Si el vector fuera un tipo de referencia (clase), get devolvería esta referencia. Los valores devueltos siempre se devuelven por valor. Incluso si los valores son referencias, estas referencias se devuelven por valor.


Es importante darse cuenta de que todo en C # se pasa por valor , a menos que especifique ref o out en la firma.

Lo que hace que los tipos de valores (y por lo tanto las struct ) sean diferentes de los tipos de referencia es que se accede a un tipo de valor directamente, mientras que se accede a un tipo de referencia a través de su referencia. Si pasa un tipo de referencia a un método, su referencia , no el valor en sí, se pasa por valor.

Para ilustrar, imagina que tenemos una clase PointClass y una struct PointStruct , definidas de forma análoga (omitiendo detalles irrelevantes):

struct PointStruct { public int x, y; } class PointClass { public int x, y; }

Y tenemos un método SomeMethod que toma estos dos tipos por valor :

static void ExampleMethod(PointClass apc, PointStruct aps) { … }

Si ahora creamos dos objetos y llamamos al método:

var pc = new PointClass(1, 1); var ps = new PointStruct(1, 1); ExampleMethod(pc, ps);

... podemos visualizar esto con el siguiente diagrama:

Como pc es una referencia, no contiene el valor en sí mismo; más bien, hace referencia a un valor (sin nombre) en otro lugar en la memoria. Esto se visualiza con el borde discontinuo y la flecha.

Pero: tanto para pc como para ps , la variable real se copia cuando se llama al método.

¿Qué ocurre si ExampleMethod reasigna las variables de argumento internamente? Vamos a revisar:

static void ExampleMethod(PointClass apc, PointStruct aps); { apc = new PointClass(2, 2); aps = new PointStruct(2, 2); }

Salida de pc y ps después de llamar al método:

pc: {x: 1, y: 1} ps: {x: 1, y: 1}

ExampleMethod cambió una copia de los valores, y los valores originales no se ven afectados.

Esto, fundamentalmente, es lo que significa "pasar por valor".

Todavía hay una diferencia entre los tipos de referencia y de valor, y eso entra en juego cuando se modifican los miembros del valor, no la variable en sí misma. Esta es la parte que hace tropezar a las personas cuando se enfrentan al hecho de que los tipos de referencia se pasan por valor. Considere un ExampleMethod diferente.

static void ExampleMethod(PointClass apc, PointStruct aps) { apc.x = 2; aps.x = 2; }

Ahora observamos el siguiente resultado después de llamar al método:

pc: {x: 2, y: 1} ps: {x: 1, y: 1}

→ El objeto de referencia se modificó, mientras que el objeto de valor no. El diagrama de arriba muestra por qué eso es: para el objeto de referencia, aunque se haya copiado la pc , el valor real de que tanto la referencia de la pc como la del apc siguen siendo idénticas, y podemos modificarlo mediante una apc . En cuanto a ps , copiamos el valor real en sí mismo en aps ; el valor original no puede ser tocado por ExampleMethod .


Los objetos se pasan por referencia y se estructuran por valor. Pero tenga en cuenta que tiene los modificadores "out" y "ref" en los argumentos.

Entonces puedes pasar una estructura por referencia así:

public void fooBar( ref Vector2 position ) { }


Los tipos de datos .NET se dividen en tipos de valor y referencia . Los tipos de valor incluyen int , byte y struct s. Los tipos de referencia incluyen string y clases.

Las estructuras son apropiadas en lugar de clases cuando solo contienen uno o dos tipos de valores (aunque incluso allí puede tener efectos secundarios no deseados).

Así que las estructuras de hecho pasan por valor y lo que estás viendo se espera.


Prólogo: C # mientras se administra todavía tiene las expresiones de memoria central creadas por C. La memoria se puede ver razonablemente como una matriz gigante donde el índice de la matriz se etiqueta como la "dirección de memoria". Un puntero es un índice numérico de esta matriz, también conocida como dirección de memoria. Los valores en esta matriz pueden ser datos o un puntero a otra dirección de memoria. Un puntero const es un valor almacenado en esta matriz en algún índice que no puede cambiar . Una dirección de memoria existe inherentemente y nunca puede cambiar, sin embargo, el valor que se encuentra en esa dirección siempre puede cambiar si no es const.

Pase por clase

Un tipo de clase / referencia (que incluye cadena) se pasa por una referencia de puntero const. La mutación afectará todos los usos de esta instancia. No puedes cambiar la dirección del objeto. Si intenta cambiar la dirección con una asignación o new , de hecho creará una variable local que comparte el mismo nombre que el parámetro en el alcance actual.

Pase por copia

Las primitivas / ValueTypes / structs (las cadenas no son ni siquiera aunque falsas como tales) se copian por completo cuando se devuelven desde un método, propiedad o se reciben como parámetro. La mutación de una estructura nunca será compartida. Si una estructura contiene un miembro de la clase, lo que se copia es la referencia del puntero. Este objeto miembro sería mutable.

Los primitivos nunca son mutables No puede mutar 1 a 2, puede mutar la dirección de memoria que actualmente se refiere a 1 a la dirección de memoria de 2 en el alcance actual.

Pase por verdadera referencia

Requiere el uso de palabras clave de out o de ref .

ref le permitirá alterar el puntero a un new objeto o asignarle un objeto existente. ref también le permitirá pasar una primitiva / ValueType / struct por su puntero de memoria para evitar copiar el objeto. También le permitirá reemplazar el puntero a una primitiva diferente si lo asigna.

out es semánticamente idéntico a ref con una diferencia menor. ref parámetros ref se deben inicializar cuando se permite que los parámetros no inicializados se inicialicen en el método que acepta el parámetro. Esto se muestra comúnmente en los métodos TryParse y elimina la necesidad de tener int x = 0; int.TryParse("5", out x) int x = 0; int.TryParse("5", out x) cuando el valor inicial de x no serviría para nada.


Sí, las estructuras heredan de ValueType y se pasan por valor. Esto también se aplica a los tipos primitivos: int, double, bool, etc. (pero no string). Las cadenas, matrices y todas las clases son tipos de referencia y se pasan por referencia.

Si quiere pasar una estructura por ref, use la palabra clave ref :

public void MyMethod (ref Vector2 position)

que pasará la estructura por ref y le permitirá modificar sus miembros.


Solo para ilustrar los diferentes efectos de pasar struct vs clase a través de métodos:

(nota: probado en LINQPad 4 )

Ejemplo

/// via http://.com/questions/9251608/are-structs-pass-by-value void Main() { // just confirming with delegates Action<StructTransport> delegateTryUpdateValueType = (t) => { t.i += 10; t.s += ", appended delegate"; }; Action<ClassTransport> delegateTryUpdateRefType = (t) => { t.i += 10; t.s += ", appended delegate"; }; // initial state var structObject = new StructTransport { i = 1, s = "one" }; var classObject = new ClassTransport { i = 2, s = "two" }; structObject.Dump("Value Type - initial"); classObject.Dump("Reference Type - initial"); // make some changes! delegateTryUpdateValueType(structObject); delegateTryUpdateRefType(classObject); structObject.Dump("Value Type - after delegate"); classObject.Dump("Reference Type - after delegate"); methodTryUpdateValueType(structObject); methodTryUpdateRefType(classObject); structObject.Dump("Value Type - after method"); classObject.Dump("Reference Type - after method"); methodTryUpdateValueTypePassByRef(ref structObject); methodTryUpdateRefTypePassByRef(ref classObject); structObject.Dump("Value Type - after method passed-by-ref"); classObject.Dump("Reference Type - after method passed-by-ref"); } // the constructs public struct StructTransport { public int i { get; set; } public string s { get; set; } } public class ClassTransport { public int i { get; set; } public string s { get; set; } } // the methods public void methodTryUpdateValueType(StructTransport t) { t.i += 100; t.s += ", appended method"; } public void methodTryUpdateRefType(ClassTransport t) { t.i += 100; t.s += ", appended method"; } public void methodTryUpdateValueTypePassByRef(ref StructTransport t) { t.i += 1000; t.s += ", appended method by ref"; } public void methodTryUpdateRefTypePassByRef(ref ClassTransport t) { t.i += 1000; t.s += ", appended method by ref"; }

Resultados

(desde el volcado de LINQPad)

Value Type - initial StructTransport UserQuery+StructTransport i 1 s one Reference Type - initial ClassTransport UserQuery+ClassTransport i 2 s two //------------------------ Value Type - after delegate StructTransport UserQuery+StructTransport i 1 s one Reference Type - after delegate ClassTransport UserQuery+ClassTransport i 12 s two, appended delegate //------------------------ Value Type - after method StructTransport UserQuery+StructTransport i 1 s one Reference Type - after method ClassTransport UserQuery+ClassTransport i 112 s two, appended delegate, appended method //------------------------ Value Type - after method passed-by-ref StructTransport UserQuery+StructTransport i 1001 s one, appended method by ref Reference Type - after method passed-by-ref ClassTransport UserQuery+ClassTransport i 1112 s two, appended delegate, appended method, appended method by ref


Una struct es un tipo de valor, por lo que siempre se pasa como un valor.

Un valor puede ser un tipo de referencia (objeto) o un tipo de valor (estructura). Lo que pasa es siempre un valor; para un tipo de referencia, pasa el valor de la referencia al mismo, para un tipo de valor, pasa el valor mismo.

El término por referencia se usa cuando usa las palabras clave ref o out para pasar un parámetro. Luego está pasando una referencia a la variable que contiene el valor en lugar de pasar el valor. Normalmente, un parámetro siempre pasa por valor .