c# - Struct que contiene tipos de referencia
.net (2)
Considera tu primer ejemplo. Tiene dos cajones, etiquetados como "dirección uno" y "dirección dos". Ambos cajones están vacíos.
MyIPEndPoint address1 = new MyIPEndPoint("127.0.0.1", 8080);
Ahora obtiene un pedazo de papel, escribe "127.0.0.1, 8080" en ese papel y lo coloca en el cajón 1.
MyIPEndPoint address2 = address1;
Ahora toma una fotocopiadora y realiza una fotocopia del papel en el cajón de "dirección uno", y coloca la copia en el "cajón de dirección dos".
address2.IP = "255.255.255.255";
address2.Port = 9090;
Ahora toma el papel en el cajón de la dirección dos y tacha lo que estaba allí, y reemplácelo con el nuevo texto.
El papel en el cajón uno no cambió. Sigue siendo el mismo de siempre.
Ahora considera tu segundo ejemplo. Ahora tiene dos cajones vacíos, igual que antes, y un libro de papel en blanco.
MyIPEndPoint address1 = new MyIPEndPoint("127.0.0.1", 8080);
Selecciona una página del libro al azar y la titula "REFERENCIA UNO". En esa página escribes "127.0.0.1". Toma un pedazo de papel suelto y escribe "REFERENCIA UNO, 8080" en ese papel y colócalo en el cajón con la etiqueta "dirección uno".
MyIPEndPoint address2 = address1;
Usted hace una fotocopia del papel en "dirección uno" y pone la copia en "dirección dos".
address2.IP.IpAsString = "255.255.255.255";
Abre el cajón "dirección dos" y ve que dice "REFERENCIA UNO". Mire el libro hasta encontrar una página que diga "REFERENCIA UNO". Se rasca lo que hay y se reemplaza con el nuevo texto.
address2.Port = 9090;
Abre el cajón "dirección dos" y borra el "8080" y lo reemplaza por "9090". Dejas la REFERENCIA UNO donde está.
Y ahora, cuando haya terminado, el cajón "address one" contiene "REFERENCE ONE, 8080", el cajón "address two" contiene "REFERENCE ONE, 9090", y el libro tiene una página que dice "REFERENCE ONE: 255.255.255.255".
¿Ahora entiendes la diferencia entre los tipos de referencia y valor?
Una estructura es un tipo de valor, por lo que si asigno una estructura a otra estructura, sus campos se copiarán en la segunda estructura. Pero, ¿qué sucede si algunos campos de la estructura son un tipo de referencia?
public struct MyIPEndPoint
{
public String IP;
public UInt16 Port;
public MyIPEndPoint(String ipAddress, UInt16 portNumber)
{
IP = ipAddress;
Port = portNumber;
}
public override string ToString()
{
return IP+":"+Port;
}
}
...
static int Main(string[] args)
{
MyIPEndPoint address1 = new MyIPEndPoint("127.0.0.1", 8080);
MyIPEndPoint address2 = address1;
address2.IP = "255.255.255.255";
address2.Port = 9090;
Console.WriteLine(address1);
Console.WriteLine(address2);
}
La salida es:
127.0.0.1:8080
255.255.255.255:9090
¿Por qué la IP
(una cadena, que es un tipo de referencia) de address1
no cambia? El mismo comportamiento ocurre si sustituyo la string
con IPAddress
IP para representar la dirección IP dentro de MyIPEndPoint
: aunque la IPAddress
es una clase (que es un tipo de referencia), no se comporta como un tipo de referencia. ¿Por qué?
De hecho, si envuelvo la string
que representa la IP usando una nueva clase simple MyIP
, el comportamiento cambia.
public class MyIP
{
public string IpAsString;
public MyIP(string s)
{
IpAsString = s;
}
public override string ToString()
{
return IpAsString;
}
}
Por supuesto, también debe ajustar la estructura MyIPEndPoint
de la siguiente manera:
public struct MyIPEndPoint
{
public MyIP IP; // modification
public UInt16 Port;
public MyIPEndPoint(String ipAddress, UInt16 portNumber)
{
IP = new MyIP(ipAddress); // modification
Port = portNumber;
}
public override string ToString()
{
return IP+":"+Port;
}
}
Finalmente en el Main
solo cambié una declaración:
MyIPEndPoint address1 = new MyIPEndPoint("127.0.0.1", 8080);
MyIPEndPoint address2 = address1;
address2.IP.IpAsString = "255.255.255.255"; // modification
address2.Port = 9090;
Console.WriteLine(address1);
Console.WriteLine(address2);
Ahora la salida es:
255.255.255.255:8080
255.255.255.255:9090
Esperaba esta salida en el primer caso. ¿Por qué en el primer caso la referencia no se comporta como se esperaba ?
Ha entendido correctamente que con structs, address1 y address2 no son el mismo objeto. Los valores fueron copiados. Sin embargo, para el campo, este es un caso simple de reasignación. No tiene nada que ver con el hecho de que la cadena es un tipo de referencia o cualquier regla especial o cualquier sugerencia de inmutabilidad. Simplemente ha reasignado una propiedad o campo con otro valor.
someStruct.SomeString = "A";
anotherStruct = someStruct;
anotherStruct.SomeString = "B"; // would never affect someStruct
Has sobrescrito la referencia en este ejemplo. El hecho de que por un breve momento, los campos de ambas estructuras contengan la misma referencia no tiene importancia. En tu segundo ejemplo, hiciste algo muy diferente.
someStruct.IP.SomeString = "A";
anotherStruct = someStruct;
anotherStruct.IP.SomeString = "B";
En este caso, el valor de IP no ha cambiado. Parte del estado de IP ha cambiado. El campo de cada estructura todavía hace referencia a la misma IP.
Poner en términos más simples
var foo = new Foo(); // Foo is class
var other = foo;
// other and foo contain same value, a reference to an object of type Foo
other = new Foo(); // was foo modified? no!
int x = 1;
int y = x;
y = 2; // was x modified? of course not.
string s = "S";
string t = s;
t = "T"; // is s "T"? (again, no)
Las variables y los campos tienen valores. Para las clases, esos valores son referencias a objetos. Dos variables o campos pueden contener la misma referencia, pero eso no significa que esas variables estén vinculadas. No están conectados de ninguna manera, simplemente tienen un valor común. Cuando reemplaza un valor para una variable o campo, la otra variable no se ve afectada.
No sobre el tema específico, pero vale la pena señalar que muchos consideran que las estructuras mutables son malas. Otros no tienen la misma opinión, o al menos no tan religiosamente. (Sin embargo, también vale la pena señalar que si la dirección hubiera sido una clase, entonces address1 y address2 mantendrían el mismo valor (la referencia al objeto Address), y la modificación del estado de address1 sería visible a través de address2 siempre y cuando ninguna dirección1 o address2 son reasignados ellos mismos. )
Si esta es una representación real de su código, valdría la pena hacer una investigación sobre estructuras mutables para que al menos tenga una comprensión completa de las diversas dificultades que puede encontrar.