Os objetos nunca ficam fora do escopo em C #, como em C ++. Eles são tratados pelo Garbage Collector automaticamente quando não são mais usados. Essa é uma abordagem mais complicada que o C ++, onde o escopo de uma variável é inteiramente determinístico. O coletor de lixo CLR passa ativamente por todos os objetos que foram criados e funciona se eles estiverem sendo usados.
Um objeto pode ficar "fora do escopo" em uma função, mas se seu valor for retornado, o GC verificará se a função de chamada mantém ou não o valor de retorno.
Definir referências de objetos null
como desnecessário, pois a coleta de lixo funciona, trabalhando para quais objetos estão sendo referenciados por outros objetos.
Na prática, você não precisa se preocupar com a destruição, apenas funciona e é ótimo :)
Dispose
deve ser chamado em todos os objetos implementados IDisposable
quando você terminar de trabalhar com eles. Normalmente você usaria um using
bloco com esses objetos da seguinte maneira:
using (var ms = new MemoryStream()) {
//...
}
EDIT No escopo variável. Craig perguntou se o escopo da variável tem algum efeito no tempo de vida do objeto. Para explicar adequadamente esse aspecto do CLR, precisarei explicar alguns conceitos de C ++ e C #.
Escopo da variável real
Nos dois idiomas, a variável só pode ser usada no mesmo escopo que foi definido - classe, função ou um bloco de instrução entre chaves. A diferença sutil, no entanto, é que, em C #, as variáveis não podem ser redefinidas em um bloco aninhado.
Em C ++, isso é perfeitamente legal:
int iVal = 8;
//iVal == 8
if (iVal == 8){
int iVal = 5;
//iVal == 5
}
//iVal == 8
Em C #, no entanto, você recebe um erro do compilador:
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
}
Isso faz sentido se você observar o MSIL gerado - todas as variáveis usadas pela função são definidas no início da função. Dê uma olhada nesta função:
public static void Scope() {
int iVal = 8;
if(iVal == 8) {
int iVal2 = 5;
}
}
Abaixo está a IL gerada. Observe que o iVal2, que é definido dentro do bloco if, é realmente definido no nível da função. Efetivamente, isso significa que o C # só tem escopo de nível de classe e função no que diz respeito à vida útil variável.
.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
Escopo C ++ e vida útil do objeto
Sempre que uma variável C ++, alocada na pilha, sai do escopo, ela é destruída. Lembre-se de que, em C ++, você pode criar objetos na pilha ou na pilha. Quando você os cria na pilha, uma vez que a execução sai do escopo, eles são retirados da pilha e destruídos.
if (true) {
MyClass stackObj; //created on the stack
MyClass heapObj = new MyClass(); //created on the heap
obj.doSomething();
} //<-- stackObj is destroyed
//heapObj still lives
Quando objetos C ++ são criados no heap, eles devem ser explicitamente destruídos, caso contrário, é um vazamento de memória. Porém, não existe esse problema com as variáveis da pilha.
Vida útil do objeto C #
No CLR, os objetos (ou seja, tipos de referência) são sempre criados no heap gerenciado. Isso é reforçado ainda mais pela sintaxe de criação de objeto. Considere esse trecho de código.
MyClass stackObj;
Em C ++, isso criaria uma instância na MyClass
pilha e chamaria seu construtor padrão. Em C #, criaria uma referência à classe MyClass
que não aponta para nada. A única maneira de criar uma instância de uma classe é usando o new
operador:
MyClass stackObj = new MyClass();
De certa forma, os objetos C # são muito parecidos com os objetos criados usando a new
sintaxe em C ++ - eles são criados no heap, mas, diferentemente dos objetos C ++, eles são gerenciados pelo tempo de execução, portanto você não precisa se preocupar em destruí-los.
Como os objetos estão sempre na pilha, o fato de as referências a objetos (ou seja, ponteiros) ficarem fora do escopo torna-se discutível. Existem mais fatores envolvidos para determinar se um objeto deve ser coletado do que simplesmente a presença de referências ao objeto.
Referências de objeto em C #
Jon Skeet comparou as referências de objeto em Java a pedaços de string anexados ao balão, que é o objeto. A mesma analogia se aplica às referências de objeto C #. Eles simplesmente apontam para um local da pilha que contém o objeto. Assim, defini-lo como nulo não tem efeito imediato no tempo de vida do objeto, o balão continua a existir, até que o GC o "abra".
Continuando pela analogia do balão, parece lógico que, uma vez que o balão não tenha cordas, ele poderá ser destruído. De fato, é exatamente assim que os objetos contados de referência funcionam em linguagens não gerenciadas. Exceto que essa abordagem não funciona muito bem para referências circulares. Imagine dois balões que são presos por uma corda, mas nenhum deles tem uma corda para mais nada. Sob regras simples de contagem, ambas continuam existindo, mesmo que todo o grupo de balões seja "órfão".
Objetos .NET são muito parecidos com balões de hélio sob o teto. Quando o teto é aberto (o GC é executado) - os balões não utilizados flutuam para longe, mesmo que haja grupos de balões presos juntos.
O .NET GC usa uma combinação de GC geracional e marca e varredura. A abordagem geracional envolve o tempo de execução que favorece a inspeção de objetos alocados mais recentemente, pois é mais provável que eles não sejam utilizados e a marcação e a varredura envolvem o tempo de execução percorrendo todo o gráfico de objetos e resolvendo se há grupos de objetos que não são utilizados. Isso lida adequadamente com o problema de dependência circular.
Além disso, o .NET GC é executado em outro thread (o chamado thread do finalizador), pois tem muito o que fazer, e isso no thread principal interromperia o programa.