ATUALIZAÇÃO : Usei essa pergunta como base para um artigo que pode ser encontrado aqui ; veja-o para uma discussão adicional deste assunto. Obrigado pela boa pergunta!
Embora a resposta de Schabse esteja correta e responda à pergunta que foi feita, há uma variante importante da sua pergunta que você não perguntou:
O que acontece se font4 = new Font()
throws depois que o recurso não gerenciado foi alocado pelo construtor, mas antes que o ctor retorne e preencha font4
com a referência?
Deixe-me deixar isso um pouco mais claro. Suponha que temos:
public sealed class Foo : IDisposable
{
private int handle = 0;
private bool disposed = false;
public Foo()
{
Blah1();
int x = AllocateResource();
Blah2();
this.handle = x;
Blah3();
}
~Foo()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.disposed)
{
if (this.handle != 0)
DeallocateResource(this.handle);
this.handle = 0;
this.disposed = true;
}
}
}
Agora temos
using(Foo foo = new Foo())
Whatever(foo);
Este é o mesmo que
{
Foo foo = new Foo();
try
{
Whatever(foo);
}
finally
{
IDisposable d = foo as IDisposable;
if (d != null)
d.Dispose();
}
}
ESTÁ BEM. Suponha que Whatever
jogue. Em seguida, o finally
bloco é executado e o recurso é desalocado. Sem problemas.
Suponha que Blah1()
jogue. Então, o lançamento acontece antes que o recurso seja alocado. O objeto foi alocado, mas o ctor nunca retorna, então foo
nunca é preenchido. Nunca inserimos o, try
portanto, nunca inserimos o finally
também. A referência do objeto ficou órfã. Eventualmente, o GC descobrirá isso e o colocará na fila do finalizador. handle
ainda é zero, então o finalizador não faz nada. Observe que o finalizador deve ser robusto diante de um objeto que está sendo finalizado e cujo construtor nunca foi concluído . Você está exigido para escrever finalizadores que são tão forte. Essa é outra razão pela qual você deve deixar os finalizadores de redação para especialistas e não tentar fazer você mesmo.
Suponha que Blah3()
jogue. O lançamento acontece depois que o recurso é alocado. Mas, novamente, foo
nunca é preenchido, nunca inserimos o finally
e o objeto é limpo pelo encadeamento do finalizador. Desta vez, o identificador é diferente de zero e o finalizador o limpa. Novamente, o finalizador está sendo executado em um objeto cujo construtor nunca foi bem-sucedido, mas o finalizador é executado de qualquer maneira. Obviamente que sim, porque desta vez, tinha trabalho a fazer.
Agora suponha que Blah2()
jogue. O lançamento acontece depois que o recurso é alocado, mas antes de handle
ser preenchido! Novamente, o finalizador será executado, mas agora handle
ainda é zero e vazamos o identificador!
Você precisa escrever um código extremamente inteligente para evitar que esse vazamento aconteça. Agora, no caso do seu Font
recurso, quem se importa? Vazamos um identificador de fonte, grande coisa. Mas se você exige de forma absolutamente positiva que todos os recursos não gerenciados sejam limpos, não importa o momento das exceções , você tem um problema muito difícil em mãos.
O CLR tem que resolver esse problema com travas. Desde C # 4, os bloqueios que usam a lock
instrução foram implementados assim:
bool lockEntered = false;
object lockObject = whatever;
try
{
Monitor.Enter(lockObject, ref lockEntered);
lock body here
}
finally
{
if (lockEntered) Monitor.Exit(lockObject);
}
Enter
foi escrito com muito cuidado para que, independentemente das exceções lançadas , lockEntered
seja definido como verdadeiro se e somente se o bloqueio foi realmente executado. Se você tiver requisitos semelhantes, o que você precisa é realmente escrever:
public Foo()
{
Blah1();
AllocateResource(ref handle);
Blah2();
Blah3();
}
e escreva AllocateResource
habilmente Monitor.Enter
assim, não importa o que aconteça dentro AllocateResource
, o handle
seja preenchido se e somente se precisar ser desalocado.
Descrever as técnicas para fazer isso está além do escopo desta resposta. Consulte um especialista se você tiver esse requisito.