Gestión de Memoria – Parte III
Parte I | Parte II | Parte IV
A pesar de que con el marco. NET no tenemos que preocuparnos activamente
sobre la gestión de memoria y recolección de basura (GC), todavía tenemos
que mantener la gestión de memoria y GC en cuenta a fin de optimizar el
rendimiento de nuestras aplicaciones. Además, tener una comprensión básica
de cómo funciona la gestión de memoria ayudará a explicar el comportamiento
de las variables con las que trabajamos en todos los programas que escribimos.
Aquí cubriremos algunos de los comportamientos que tenemos que tener en
cuenta al pasar parámetros a los métodos. Aqui cubriremos un tema que surge
al tener variables de referencia en el Heap y cómo hacemos para posicionarlas
a través del uso de ICloneable.
Una Copia no es una Copia
Para definir claramente el problema, vamos a examinar lo que ocurre cuando
hay un Tipo Valor y un Tipo Referencia en el Heap. En primer lugar veremos
el Tipo Valor. Tomemos las siguientes Clase y Estructura. Tenemos una Clase
Persona que tiene por características un Nombre y dos Zapatos y tiene un
único método CopiaPersona() esto para facilitar la creación de nuevas Personas.
public struct Zapato
{
public string Color;
}
public class Persona
{
public string Nombre;
public Zapato Zapatoderecho;
public Zapato ZapatoIzquierdo;
public Persona CopiarPersona()
{
Persona nuevaPersona = new Persona();
nuevaPersona.Nombre = Nombre;
nuevaPersona.ZapatoIzquierdo = ZapatoIzquierdo;
nuevaPersona.ZapatoDerecho = ZapatoDerecho;
return nuevaPersona;
}
public override string ToString()
{
return (Nombre + ", tiene un zapato de color " + ZapatoDerecho.Color +
" en el pie derecho y, un zapato de color " +
ZapatoIzquierdo.Color + " en el pie izquierdo.");
}
}
Nuestra clase Persona es una variable Tipo y como la estructura Zapato es
un elemento de la clase de ambos terminan por localizarse en le Heap
Cuando ejecutamos el siguiente método:
public static void Main()
{
Clase1 pgm = new Clase1();
Persona Pedro = new Persona();
Pedro.Nombre = "Pedro";
Pedro.ZapatoIzquierdo = new Zapato();
Pedro.ZapatoDerecho = new Zapato();
Pedro.ZapatoIzquierdo.Color = Pedro.ZapatoDerecho.Color = "Negro";
Persona Juan = Pedro.CopiarPersona();
Juan.Name = "Juan";
Juan.ZapatoIzquierdo.Color = Juan.ZapatoDerecho.Color = "Azul";
Console.WriteLine(Pedro.ToString());
Console.WriteLine(Juan.ToString());
}
Y obtenemos el resultado esperado:
Pedro, tiene un un zapato de color Negro en el pie derecho y, un zapato de
color Negro en el pie izquierdo.
Juan, tiene un un zapato de color Azul en el pie derecho y, un zapato de
color Azul en el pie izquierdo.
¿Qué sucede si definimos la estructura Zapato en un Tipo Referencia?
Aquí tendremos un problema si cambiamos la estructura Zapato de la forma
que se presenta a continuación:
public class Zapato
{
public string Color;
}
Y al ejecutar el mismo código de Main temos el siguiente resultado:
Pedro, tiene un un zapato de color Azul en el pie derecho y, un zapato de
color Azul en el pie izquierdo.
Juan, tiene un un zapato de color Azul en el pie derecho y, un zapato de
color Azul en el pie izquierdo.
Aquí existe claramente un error, observe lo que sucede en el Heap
Debido a que ahora se está usando zapato como un Tipo Referencia en
lugar de un Tipo Valor y cuando realizamos la copia del contenido de un
Tipo Referencia, sólo copiamos el puntero (y no el objeto real que se
apunta), tenemos que hacer algún trabajo extra, para que nuestro Tipo
Referencia Zapato se comporte como un Tipo Valor.
Afortunadamente, tenemos una interfaz que nos ayudará a salir de este
problema: IClonable. Esta interfaz es básicamente un contrato con el cual
todas las Personas estarán de acuerdo, y define cómo un Tipo Referencia
se duplica con el fin de evitar que nuestro "Zapato compartido" de error.
Todas nuestras clases que necesitan ser "clonadas" debe usar la interfaz
ICloneable, incluyendo el tipo Zapato.
ICloneable consiste de un método: Clone()
public object Clone()
{
}
A continuación una implementación correcta de la clase Zapato
public class Zapato : ICloneable
{
public string Color;
#region Miembros ICloneable
public object Clone()
{
Zapato NuevoZapato = new Zapato();
NuevoZapato.Color = Color.Clone() as string;
return NuevoZapato;
}
#endregion
}
Dentro del método Clone(), creamos un nuevo Zapato, clonamos todos
los TiposValor y Referencia y devolvemos el nuevo objeto. Note que la
clase string ya implementa la interface IClonable por lo cual podemos
llamar Color.Clone().Dado que Clone() una referencia a un objeto, tenemos
que "volver a escribir" la referencia antes que nos sea posible establecer
el Color del Zapato.
A continuación, el método CopiarPersona() que necesitaremos para clonar
Zapatos
public Persona CopiarPersona()
{
Persona nuevaPersona = new Persona();
nuevaPersona.Nombre = Nombre;
nuevaPersona.ZapatoIzquierdo = ZapatoIzquierdo.Clone() as Zapato;
nuevaPersona.ZapatoDerecho = ZapatoDerecho.Clone() as Zapato;
return nuevaPersona;
}
Ahora, cuando ejecutamos Main()
public static void Main()
{
Class1 pgm = new Class1();
Persona Pedro = new Persona();
Pedro.Nombre = "Pedro";
Pedro.ZapatoIzquierdo = new Zapato();
Pedro.ZapatoDerecho = new Zapato();
Pedro.ZapatoIzquierdo.Color = Pedro.ZapatoDerecho.Color = "Negro";
Persona Juan = Pedro.CopiarPersona();
Juan.Nombre = "Juan";
Juan.ZapatoIzquierdo.Color = Juan.ZapatoDerecho.Color = "Azul";
Console.WriteLine(Pedro.ToString());
Console.WriteLine(Juan.ToString());
}
Y obtenemos:
Pedro, tiene un un zapato de color Negro en el pie derecho y, un zapato de
color Negro en el pie izquierdo.
Juan, tiene un un zapato de color Azul en el pie derecho y, un zapato de
color Azul en el pie izquierdo.
que es lo esperado
Así que, como práctica general se recomienda, clonar siempre los Tipos
Referencia y copiar los Tipos Valor. (de esta forma evitará muchos
dolores de cabeza al depurar sus aplicaciones para detectar errores)
Aplicando la filosofía de pocos dolores de cabeza, modificamos entonces
nuestra clase Persona de forma a aplicar IClonable, en lugar de utilizar
el método CopiaPersona()
public class Persona: ICloneable
{
public string Nombre;
public Zapato ZapatoDerecho;
public Zapato ZapatoIzquierdo;
public override string ToString()
{
return (Nombre + ", tiene un zapato de color " + ZapatoDerecho.Color +
" en el pie derecho y, un zapato de color " +
ZapatoIzquierdo.Color + " en el pie izquierdo.");
}
#region ICloneable Members
public object Clone()
{
Persona nuevaPersona = new Persona();
nuevaPersona.Nombre = Nombre.Clone() as string;
nuevaPersona.ZapatoIzquierdo = ZapatoIzquierdo.Clone() as Zapato;
nuevaPersona.ZapatoDerecho = ZapatoIzquierdo.Clone() as Zapato;
return nuevaPersona;
}
#endregion
}
Y cambiamos el método Main() para usar Persona.Clone()
public static void Main()
{
Class1 pgm = new Class1();
Persona Pedro = new Persona();
Pedro.Nombre = "Pedro";
Pedro.ZapatoIzquierdo = new Zapato();
Pedro.ZapatoDerecho = new Zapato();
Pedro.ZapatoIzquierdo.Color = Pedro.ZapatoDerecho.Color = "Negro";
Persona Juan = Pedro.Clone() as Persona;
Juan.Nombre = "Juan";
Juan.ZapatoIzquierdo.Color = Juan.ZapatoDerecho.Color = "Azul";
Console.WriteLine(Pedro.ToString());
Console.WriteLine(Juan.ToString());
}
Y obtenemos como salida:
Pedro, tiene un un zapato de color Negro en el pie derecho y, un zapato de
color Negro en el pie izquierdo.
Juan, tiene un un zapato de color Azul en el pie derecho y, un zapato de
color Azul en el pie izquierdo.
que es lo esperado
Algo interesante a destacar es que el operador de asignación (el "signo = ")
para la clase System.String en realidad clona la string por lo que no tenemos
que preocuparnos por referencias duplicadas. Sin embargo, tenemos que
observar para la aglomeración de objetos en la memoria. Si observamos los
diagramas anteriores, presentamos la string como un Tipo Referencia cuando
en realidad es un Puntero a otro objeto que existe en el Heap, pero para
simplificar fue presentado como un Tipo Valor.
Conclusión
Como práctica general, si tenemos pensado copiar objetos, debemos poner
en práctica el uso de ICloneable. Esto permite a nuestros Tipos Referencia
imitar en alguna medida el comportamiento de un Tipo Valor. Como puede ver,
es muy importante no perder de vista qué tipo de variable estamos tratando,
debido a las diferencias en la forma en que se asigna la memoria para los
Tipos Valor y los Tipos Referencia.
En la próxima parte de esta serie, veremos una forma de reducir el rastro
de nuestro código en la memoria.
Sharing is caring. Share this article now!