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 font4com 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 Whateverjogue. Em seguida, o finallybloco é 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 foonunca é preenchido. Nunca inserimos o, tryportanto, nunca inserimos o finallytambém. A referência do objeto ficou órfã. Eventualmente, o GC descobrirá isso e o colocará na fila do finalizador. handleainda é 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, foonunca é preenchido, nunca inserimos o finallye 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 handleainda é 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 Fontrecurso, 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 lockinstruçã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);
}
Enterfoi escrito com muito cuidado para que, independentemente das exceções lançadas , lockEnteredseja 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 AllocateResourcehabilmente Monitor.Enterassim, não importa o que aconteça dentro AllocateResource, o handleseja 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.