Los objetos nunca salen del alcance en C # como lo hacen en C ++. El recolector de basura los trata automáticamente cuando ya no se usan. Este es un enfoque más complicado que C ++ donde el alcance de una variable es completamente determinista. El recolector de basura CLR revisa activamente todos los objetos que se han creado y funciona si se están utilizando.
Un objeto puede quedar "fuera de alcance" en una función, pero si se devuelve su valor, entonces GC vería si la función de llamada mantiene o no el valor de retorno.
No null
es necesario establecer referencias de objeto ya que la recolección de basura funciona al determinar a qué objetos hacen referencia otros objetos.
En la práctica, no tienes que preocuparte por la destrucción, simplemente funciona y es genial :)
Dispose
debe invocarse en todos los objetos que se implementan IDisposable
cuando termina de trabajar con ellos. Normalmente usarías un using
bloque con esos objetos así:
using (var ms = new MemoryStream()) {
//...
}
EDITAR En alcance variable. Craig ha preguntado si el alcance variable tiene algún efecto en la vida útil del objeto. Para explicar adecuadamente ese aspecto de CLR, necesitaré explicar algunos conceptos de C ++ y C #.
Alcance variable real
En ambos idiomas, la variable solo se puede usar en el mismo alcance que se definió: clase, función o un bloque de enunciado encerrado entre llaves. La sutil diferencia, sin embargo, es que en C #, las variables no se pueden redefinir en un bloque anidado.
En C ++, esto es perfectamente legal:
int iVal = 8;
//iVal == 8
if (iVal == 8){
int iVal = 5;
//iVal == 5
}
//iVal == 8
En C #, sin embargo, obtienes un error de compilación:
int iVal = 8;
if(iVal == 8) {
int iVal = 5; //error CS0136: A local variable named 'iVal' cannot be declared in this scope because it would give a different meaning to 'iVal', which is already used in a 'parent or current' scope to denote something else
}
Esto tiene sentido si observa el MSIL generado: todas las variables utilizadas por la función se definen al comienzo de la función. Echa un vistazo a esta función:
public static void Scope() {
int iVal = 8;
if(iVal == 8) {
int iVal2 = 5;
}
}
A continuación se muestra la IL generada. Tenga en cuenta que iVal2, que se define dentro del bloque if, se define realmente a nivel de función. Efectivamente, esto significa que C # solo tiene un alcance de nivel de clase y función en lo que respecta a la vida útil de la variable.
.method public hidebysig static void Scope() cil managed
{
// Code size 19 (0x13)
.maxstack 2
.locals init ([0] int32 iVal,
[1] int32 iVal2,
[2] bool CS$4$0000)
//Function IL - omitted
} // end of method Test2::Scope
Alcance de C ++ y vida útil del objeto
Cada vez que una variable de C ++, asignada en la pila, queda fuera de alcance, se destruye. Recuerde que en C ++ puede crear objetos en la pila o en el montón. Cuando los crea en la pila, una vez que la ejecución abandona el alcance, se sacan de la pila y se destruyen.
if (true) {
MyClass stackObj; //created on the stack
MyClass heapObj = new MyClass(); //created on the heap
obj.doSomething();
} //<-- stackObj is destroyed
//heapObj still lives
Cuando se crean objetos C ++ en el montón, deben destruirse explícitamente; de lo contrario, se trata de una pérdida de memoria. Sin embargo, no existe tal problema con las variables de pila.
C # Objeto de por vida
En CLR, los objetos (es decir, los tipos de referencia) siempre se crean en el montón administrado. Esto se ve reforzado por la sintaxis de creación de objetos. Considere este fragmento de código.
MyClass stackObj;
En C ++ esto crearía una instancia MyClass
en la pila y llamaría a su constructor predeterminado. En C # crearía una referencia a la clase MyClass
que no apunta a nada. La única forma de crear una instancia de una clase es utilizando el new
operador:
MyClass stackObj = new MyClass();
En cierto modo, los objetos de C # son muy parecidos a los objetos que se crean utilizando la new
sintaxis en C ++: se crean en el montón pero, a diferencia de los objetos de C ++, son administrados por el tiempo de ejecución, por lo que no tiene que preocuparse por destruirlos.
Dado que los objetos siempre están en el montón, el hecho de que las referencias a objetos (es decir, punteros) se salgan del alcance se vuelve discutible. Hay más factores involucrados en determinar si un objeto se va a recolectar que simplemente la presencia de referencias al objeto.
C # referencias de objeto
Jon Skeet comparó las referencias de objetos en Java con trozos de cuerda que están unidos al globo, que es el objeto. La misma analogía se aplica a las referencias de objetos de C #. Simplemente apuntan a una ubicación del montón que contiene el objeto. Por lo tanto, establecerlo como nulo no tiene un efecto inmediato en la vida útil del objeto, el globo continúa existiendo, hasta que el GC lo "explota".
Continuando con la analogía del globo, parecería lógico que una vez que el globo no tiene ataduras, puede ser destruido. De hecho, así es exactamente cómo funcionan los objetos contados de referencia en lenguajes no administrados. Excepto que este enfoque no funciona para referencias circulares muy bien. Imagine dos globos que están unidos por una cuerda pero ninguno de los globos tiene una cuerda para nada más. Bajo simples reglas de conteo de referencias, ambos continúan existiendo, a pesar de que todo el grupo de globos está "huérfano".
Los objetos .NET son muy parecidos a los globos de helio bajo un techo. Cuando se abre el techo (el GC funciona): los globos no utilizados se alejan flotando, a pesar de que puede haber grupos de globos unidos entre sí.
.NET GC utiliza una combinación de GC generacional y marca y barrido. El enfoque generacional implica el tiempo de ejecución que favorece la inspección de los objetos que se han asignado más recientemente, ya que es más probable que no se usen y marcar y barrer implica el tiempo de ejecución que recorre todo el gráfico de objetos y funciona si hay grupos de objetos que no se utilizan. Esto trata adecuadamente con el problema de dependencia circular.
Además, .NET GC se ejecuta en otro subproceso (llamado subproceso finalizador) ya que tiene bastante que hacer y hacerlo en el subproceso principal interrumpiría su programa.