Ads by Smowtion

Gestión de Memoria – Parte IV

Parte I | Parte II | Parte III

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í vamos a examinar el Recolector de Basura (GC) y algunas concejos para
mantener nuestras aplicaciones funcionando de manera eficiente.

Graphing

Representemos el papel del GC. Si somos responsables de "limpiar la
basura" necesitamos un plan para hacerlo con eficacia. Obviamente,
tenemos que determinar lo que es basura y lo que no lo es.

Con el fin de determinar lo que debe mantenerse, lo primero que se nos
puede ocurrir es pensar que todo lo que no se utiliza es basura.

Imaginemos que vivimos con nuestros dos buenos amigos: José Ismael
Torres (JIT) y Cecilia Lorena Ramirez (CLR). José y Cecilia no pueden
perder de vista lo que están utilizando y nos dan una lista de cosas
que necesitan mantener a su alcance.

Lo primero que hacemos es crear una lista inicial que llamaremos "raíz",
tiene esta designación, puesto que, será utilizada como punto de partida.
A continuación creamos una lista maestra para organizar todo lo que está
en la casa y que se desea conservar. Y tenemos por último una lista de
trabajo con todas las tareas que debemos ejecutar. Todas las cosas que
son necesarias para ejecutar nuestra lista de trabajo se añaden a nuestra
lista maestra.

Veamos como el GC funciona. Este recibe una lista "raíz" con los objetos
que son demandados por el compilador Just-In-Time (JIT) y por el Common
Language Runtime (CLR) (¿recuerda José y Cecilia?). A partir de aquí,
construimos a través de búsquedas recursivas de referencia a los objetos
que son necesarios para una organización correcta de lo que se debe
mantener.


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.


Gestión de Memoria – Parte II

Parte I | Parte III | 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.

En la Parte I hemos cubierto los fundamentos del Heap y la funcionalidad del
Stack y donde los Tipos Variables y los Tipos Referencia son asignados cuando
nuestro programa se ejecuta. También se cubrió la idea básica de lo que es un
puntero.

Parámetros.

Aquí está la vista detallada de lo que sucede cuando nuestro código se ejecuta.
Hemos cubierto las bases de lo que ocurre cuando hacemos una llamada al
método en la Parte I. Ahora, vamos a entrar en más detalles ...

Cuando hacemos una llamada a un método, esto es lo que sucede:

1. Se asigna espacio en el Stack para guardar la información necesaria para la
ejecución de nuestro método (llamado de Stack Frame). Esto incluye la
dirección de llamada a (un puntero), que es básicamente una instrucción GOTO
de modo que cuando el hilo termine su ejecución, nuestro método tenga
conocimiento de dónde debe ir a fin de continuar la ejecución.
2. Los parámetros del método son copiados. Aquí nos fijaremos con más atención.
3. El control se pasa al método que se ha compilado JIT y el hilo inicia la
ejecución del código. Por lo tanto, tenemos otro método representado por una
Stack Frame en la "llamada al Stack".

El código

public int SumaDos(int punteroValor)
{
int resultado;
resultado = punteroValor + 2;
return resultado;
}

Hará que el Stack tenga este aspecto:



NOTA: el método no existe en el Stack, y se presenta aquí sólo como referencia
al inicio del Stack Frame.

Como se discutió en la Parte I, la colocación de parámetros en el Stack se
tratan de manera diferente dependiendo de si los parámetros son de un Tipo
Valor o un Tipo Referencia.

Pasando Tipos Valor

Aquí está el truco con Tipos Valor ...

En primer lugar, cuando estamos pasando un Tipo Valor, se asigna el espacio
y el valor de nuestro tipo y a continuación se copia en el nuevo espacio en
el Stack. Observe el siguiente método:

class Clase1
{
public void Go()
{
int x = 3;
SumaTres(x);
Console.WriteLine(x.ToString());
}

public int SumaTres(int punteroValor)
{
punteroValue += 3;
return punteroValor;
}
}

A medida que el método se ejecuta, el espacio para "x" se coloca en el Stack
con un valor de 3.



A continuación, SumaTres() se coloca en el Stack con espacio para sus parámetros y el valor se copia, bit a bit de x.



Cuando SumaTres() ha terminado la ejecución, el hilo se pasa de nuevo a Go()
y porque SumaTres() se ha completado, punteroValor es esencialmente "retirado":



Así que es lógico que la salida de nuestro código sea "3", ¿no? El punto es
que cualquier parámetro pasado a un método de Tipo Valor es una copia al
carbón y continuamos contando con el valor de la variable original el cual
aún se conserva.

Una cosa a tener en cuenta es que si tenemos un Tipo Valor muy grande (como
una gran estructura) y queremos pasarlo al Stack, lo cual puede ser muy
costoso en términos de espacio y los ciclos de procesador necesarios para
efectuar la copia. El Stack no tiene un espacio infinito y al igual que
cuando llenamos un vaso con agua del grifo, se puede desbordar. La estructura
esta formada por Tipos Valor que pueden ser bastante grande y tenemos que
ser conscientes de cómo la estamos manejando.

Aquí tenemos una estructura bastante grande

public struct MiEstructura
{
long a, b, c, d, e, f, g, h, i, j, k, l, m;
}

Observe a continuación lo que sucede cuando ejecutamos Go() y llegamos al
método HacerAlgo():

public void Go()
{
MiEstructura x = new MiEstructura();
HacerAlgo(x);
}

public void HacerAlgo(MiEstructura punteroValor)
{
// HACER ALGO AQUI....
}



Esto puede ser proceso ineficiente. Imagine que pasamos MiEstructura un par
de miles de veces.

¿Cómo podemos solucionar este problema? Con el recurso de una referencia al
Tipo Valor original de la siguiente manera:

public void Go()
{
MyStruct x = new MyStruct();
DoSomething(ref x);
}

public struct MiEstructura
{
long a, b, c, d, e, f, g, h, i, j, k, l, m;
}


public void HacerAlgo(ref MiEstructura punteroValor)
{
// HACER ALGO AQUI....
}

De esta manera nos encontramos con una asignación más eficiente de los
objetos en la memoria.



Lo único que tenemos que tener en cuenta al pasar nuestro Tipo Valor por referencia es que tenemos acceso al valor el Tipo Valor. Aquello que cambiamos en punteroValor se cambia en x. Usando el código siguiente, nuestros resultados van a ser "12345", porque la realidad es que punteroValor esta apuntando para el espacio de memoria donde se declaró originalmente nuestra variable x.

public void Go()
{
MiEstructura x = new MiEstructura();
x.a = 3;
HacerAlgo(ref x);

Console.WriteLine(x.a.ToString());
}

public void HacerAlgo(ref MiEstructura punteroValor)
{
punteroValor.a = 12345;
}

Pasar Tipos Referencia

Los parámetros Tipos Referencia se pasan de forma es similar a los Tipos Valor
por referencia como en el ejemplo anterior

Si utilizamos el Tipo Valor

public class MiEntero
{
public int MiValor;
}

Y llamamos al método Go(), MiEntero acaba por localizarse en el Heap, porque
es un Tipo Referencia:

public void Go()
{
MiEntero x = new MiEntero();
}



Si ejecutamos Go() con el siguiente código...

public void Go()
{
MiEntero x = new MiEntero();
x.MiValor = 3;
HacerAlgo(x);

Console.WriteLine(x.MiValor.ToString());
}

public void HacerAlgo(MiEntero punteroValor)
{
punteroValor.MiValor = 12345;
}

Esto es lo que ocurre...



1. A partir de la llamada al método Go() la variable x va al Stack.
2. Al iniciar la llamada al método HacerAlgo() el parámetro punteroValor va en la
Stack.
3. El valor de x (la dirección de MiEntero en el Stack) se copia en punteroValor

Así que es lógico que cuando cambie la propiedad MiValor del objeto MiEntero en el Heap con punteroValor por esta razón se obtiene entonces el valor "12345" al referir el objeto en el Heap a través de x.

Así que, aquí es donde se pone interesante el asunto.
¿Qué sucede cuando se pasa un Tipo Referencia por referencia?

Si tenemos una Clase Cosas con Animal y Vegetal representando ambos a cosas:

public class Cosa
{
}

public class Animal:Cosa
{
public int Peso;
}

public class Vegetal:Cosa
{
public int Largo;
}

y ejecutamos el siguiente método Go()

public void Go()
{
Cosa x = new Animal();

Cambia(ref x);

Console.WriteLine(
"x is Animal : "
+ (x is Animal).ToString());

Console.WriteLine(
"x is Vegetal : "
+ (x is Vegetal).ToString());

}

public void Cambia(ref Cosa punteroValor)
{
punteroValor = new Vegetal();
}

Convertimos la variable x en un Vegetal

x is Animal : false
x is Vegetal : true

Veamos que es lo que ocurre:



1. Empezamos con la llamada al método Go(), el puntero x es colocado en
el Stack
2. El objeto Animal es colocado en el Heap
3. Iniciamos la llamada al método Cambia(), colocamos el punteroValor en
el Stack el cual apunta para x.



4. El objeto Vegetal es colocado en el Heap
5. El valor de x se cambia a través de punteroValor de Animal para Vegetal

Si no pasamos la Cosa por referencia, vamos a mantener al objeto animal y
obtener los resultados opuestos de lo que tenemos

Conclusión

Hemos visto cómo se maneja la transmisión de parámetros en la memoria y
ahora sabemos qué atenernos. En la siguiente parte de esta serie, vamos a
observar lo que ocurre con las variables de referencia que viven en el
Stack y la manera de superar algunas cuestiones que pueden ocurrir al
momento de efectuar copias de objetos.

Hasta la próxima parte de esta serie...


Gestión de Memoria - Parte I

Parte II | Parte III | Parte IV

A pesar de que con .NET no tenemos que preocuparnos con la gestión de memoria, aquí intentaremos tener una comprensión básica de cómo funciona. Esto nos ayudará a comprehender el comportamiento de las variables con las que trabajamos en los programas que escribimos. Estas nociones nos pueden ayudar a escribir programas más eficientes en su desempeño.

El .NET framework cuando ejecuta su programa guarda los elementos en dos tipos de lugares en la memoria:

1. El Stack.
2. El Heap.

Tanto el Stack como el Heap actúan en la ejecución del programa y residen en la memoria operativa de la máquina.

Cuál es la diferencia entre Stack y Heap?

Bueno, el Stack es responsable por hacer el seguimiento de la ejecución de lo que está en nuestro código. El Heap es más o menos responsable por hacer seguimiento de la mayor parte de nuestros datos.

El Stack lo podemos imaginar como un conjunto de cajas ordenadas una encima de otra. Cada vez que llamamos a un método, para hacer el seguimiento de lo que ocurre en la aplicación colocamos una caja en la parte superior del Stack a esta caja se le llama “Frame”. Una vez que el método es ejecutado retiramos esta caja y la tiramos a la basura. Procedemos entonces, a utilizar la caja anterior que ahora se encuentra en la parte superior del Stack. El Heap es similar, solo que su función es mantener la información. Lo que existe en el Heap puede ser accedido en cualquier momento sin limitaciones en lo que se puede acceder como ocurre en el Stack.

El Stack se mantiene por sí mismo, lo que significa que básicamente se encarga de su gestión propia memoria. Cuando el cuadro de arriba ya no se utiliza, es expulsado y nada más ocurre. El Heap, en cambio, tiene que preocuparse por la recolección de basura (GC) - que trata de mantener la limpieza del Heap.



La imagen anterior, aunque no es una verdadera representación de lo que está pasando en la memoria, nos ayuda a distinguir un Stack de un Heap.

Que elementos tenemos en el Stack y en el Heap?

Tenemos cuatro tipos principales de elementos que se pueden poner en el Stack y Heap cuando nuestro código se está ejecutando:

1. Tipos Valores
2. Tipos Referencia
3. Punteros
4. Instrucciones

Tipos Valores

En C #, todas las “cosas", declaradas de tipos de la siguiente lista son Tipos Valor (porque pertenecen al espacio de nombres System.ValueType):

• bool
• byte
• char
• decimal
• double
• enum
• float
• int
• long
• sbyte
• short
• struct
• uint
• ulong
• ushort

Tipos Referencia

Todas las "cosas" declaradas de tipos de la siguiente lista son Tipos Referencia (y heredan de System.Object ... excepto, por supuesto, para el objeto que es el objeto System.Object):

• class
• interface
• delegate
• object
• string

Punteros

El tercer tipo de “cosas” que existen en nuestro esquema de memoria son las referencias a un tipo, conocidas con frecuencia como Punteros. Un Puntero (o referencia) es un fragmento de espacio en la memoria que apunta a otro espacio en la memoria. Un puntero ocupa un espacio igual que cualquier otra cosa que estamos poniendo en la pila y pila y su valor puede ser una dirección de memoria o nulo.

Instrucciones

Sobre la marcha comprenderemos lo que son las instrucciones

¿Cómo se decide lo que va a donde?

Bien, pasemos a la parte divertida...
Existen dos reglas de oro para tal:

1. Un Tipo Referencia siempre va en el Heap. ¿Fácil no?
2. Los Tipos Valor y los Punteros van siempre donde fueron declarados. Esto es
un poco más complejo y necesita un poco de comprensión de cómo funciona el
Stack, para de esta forma averiguar donde las "cosas" se declaran.

El Stack, como se mencionó anteriormente, es responsable de mantener la pista de dónde está cada subproceso durante la ejecución de nuestro código. Usted puede
pensar en él como un hilo de "estado" y cada hilo tiene su propio Stack. Cuando nuestro código realiza una llamada a ejecutar un método del hilo comienza a
ejecutar las instrucciones que se han compilado JIT y que existen en la tabla de métodos, también pone a los parámetros del método en la pila de subprocesos.

Luego, a medida que avanzamos a través del código y actuamos sobre las variables al ejecutar el método las cuales se colocan en la parte superior del Stack. Esto será más fácil de entender con el siguiente ejemplo.

public int SumaDos(int punteroValor)
{
int resultado;
resultado = punteroValor + 2;
return resultado;
}

Esto es lo que sucede en la parte superior del Stack. Tenga en cuenta que lo que estamos viendo es colocado encima de muchos otros elementos que ya existen en el Stack:

Una vez que ejecutamos el método, sus parámetros se colocan en el Stack (vamos a hablar más acerca de como pasar parámetros más adelante).

NOTA : El método no existe en el Stack solo se ilustra como referencia.



A continuación, el control (el hilo de ejecución del método) se pasa a las instrucciones de SumaDos(), que existe en nuestro tipo de tabla de métodos, una compilación JIT se realiza si esta es la primera vez que se llama al método



A medida que el método se ejecuta, necesitamos de memoria para la variable "resultado" y se asigna en el Stack.



Se finaliza la ejecución del método y se devuelve el resultado.



Y toda la memoria asignada en el Stack se limpia por desplazamiento del puntero a la dirección de memoria disponible donde SumaDos() tenia inicio y bajamos al método anterior en el Stack.



En este ejemplo, nuestra variable "resultado" se coloca en el Stack. De hecho, cada vez que un Tipo Valor se declara en el cuerpo de un método, este se colocará en el Stack.

Ahora, los Tipos Valor por veces también se colocan en el Heap. Recuerde la regla, Tipos Valor siempre van a donde fueron declarados? Bueno, si un Tipo Valor se declara fuera de un método, pero dentro de un Tipo Referencia se colocará en el Tipo Referencia del Heap.

Si tenemos la siguiente clase MiEntero (que es un Tipo Referencia porque es una clase):

public class MiEntero
{
public int MiValor;
}

y el siguiente método es ejecutado:

public MiEntero SumaDos(int punteroValor)
{
MiEntero resultado = new MiEntero();
resultado.MiValor = punteroValor + 2;
return resultado;
}

Al igual que antes, el hilo se inicia con la ejecución del método y sus parámetros se colocan en el Stack del hilo.



Ahora es cuando se pone interesante ...
Porque MiEntero es un Tipo Referencia, que se coloca en el Heap y que es referenciado por un puntero en el Stack.



Después de terminar la ejecución de SumaDos() (como en el primer ejemplo), procedemos a una limpieza ...



Y de esta forma nos quedamos con un MiEntero huérfano en el Heap (ya no existe ningun puntero en el Stack haciendo referencia a MiEntero en el Heap)!



Aquí es donde el recolector de basura (GC) entra en juego. Una vez que nuestro programa llega a un umbral de memoria y necesitamos de más espacio en el Heap,
el GC se iniciará. El CG detendrá todos los hilos en marcha (una Parada Total),
para localizar todos los objetos en el Heap que no están siendo utilizados por
el programa principal y eliminarlos. El CG entonces reorganiza todos los objetos
que quedan en el Heap para crear espacio y ajustar todos los punteros de estos objetos, tanto en la Stack como en el Heap. Como se puede imaginar, esto puede
ser muy costoso en términos de rendimiento, ahora puede observar el por qué
puede ser importante prestar atención a lo que hay en el Stack y en el Heap
cuando se trata de escribir código de alto rendimiento.

Ok ... pero ¿cómo me afecta?
Buena pregunta.

Cuando estamos usando de Tipos Referencia, estamos tratando con los punteros
que reflejan el tipo, no con la cosa misma. Cuando estamos usando Tipos Valores, estamos usando la cosa misma.

Para entender mejor esto, tenemos el siguiente ejemplo:

Si ejecutamos el siguiente código

public int RetornaValor()
{
int x = new int();
x = 3;
int y = new int();
y = x;
y = 4;
return x;
}

Vamos a obtener el valor 3. Bastante simple, ¿verdad?

Sin embargo, si estamos usando la clase MiEntero de antes

public class MiEntero
{
public int MiValor;
}

y el siguiente método es ejecutado:

public int RetornaValor2()
{
MiEntero x = new MiEntero();
x.MiValor = 3;
MiEntero y = new MiEntero();
y = x;
y.MiValor = 4;
return x.MiValor;
}

¿Qué obtenemos? ... 4!

¿Por qué? ... ¿Cómo x.MiValor puede llegar a ser 4? ... Observemos lo que estamos haciendo y veamos si tiene sentido:

En el primer ejemplo todo va según lo previsto:

public int RetornaValor()
{
int x = 3;
int y = x;
y = 4;
return x;
}



En el siguiente ejemplo, no conseguimos "3" ya que ambas variables "x" e "y" apuntan al mismo objeto en el Heap.

public int RetornaValor2()
{
MyInt x;
x.MyValue = 3;
MyInt y;
y = x;
y.MyValue = 4;
return x.MyValue;
}



Esperemos que con esto tenga una mejor comprensión de las diferencias básicas entre Tipo Valor y variables de Tipo Referencia en C # y una comprensión básica de lo que es un puntero y cuando se utiliza. En la siguiente parte de esta serie, vamos a llegar más lejos en el manejo de la memoria y hablaremos específicamente acerca de los parámetros de métodos.

Hasta la próxima parte de esta serie...