C # me permite fazer o seguinte (exemplo do MSDN):
using (Font font3 = new Font("Arial", 10.0f),
font4 = new Font("Arial", 10.0f))
{
// Use font3 and font4.
}
O que acontece se font4 = new Font
jogar? Pelo que entendi, o font3 vazará recursos e não será descartado.
- Isso é verdade? (font4 não será eliminado)
- Isso significa que
using(... , ...)
deve ser evitado completamente em favor do uso aninhado?
c#
using
using-statement
Benjamin Gruenbaum
fonte
fonte
using(... , ...)
fosse compilado em blocos aninhados de qualquer maneira, mas não tenho certeza disso.using
nada, o GC ainda irá coletá-lo.finally
bloco, não teria entrado no bloco até que todos os recursos fossem construídos.using
para atry
-finally
, a expressão de inicialização é avaliada fora dotry
. Portanto, é uma questão razoável.Respostas:
Não.
O compilador irá gerar um
finally
bloco separado para cada variável.A especificação (§8.13) diz:
fonte
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:
Deixe-me deixar isso um pouco mais claro. Suponha que temos:
Agora temos
Este é o mesmo que
ESTÁ BEM. Suponha que
Whatever
jogue. Em seguida, ofinally
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ãofoo
nunca é preenchido. Nunca inserimos o,try
portanto, nunca inserimos ofinally
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 ofinally
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 dehandle
ser preenchido! Novamente, o finalizador será executado, mas agorahandle
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: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:e escreva
AllocateResource
habilmenteMonitor.Enter
assim, não importa o que aconteça dentroAllocateResource
, ohandle
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.
fonte
Blah
chamadas de método. O que impede que ThreadAbortException aconteça em qualquer um desses pontos?AllocateResource
mas antes da atribuição parax
. AThreadAbortException
pode acontecer nesse ponto. Todo mundo aqui parece estar perdendo o meu ponto, que é a criação de um recurso e a atribuição de uma referência a ele a uma variável não é uma operação atômica . Para resolver o problema que identifiquei, você deve torná-lo uma operação atômica.Como um complemento à resposta de @SLaks, aqui está o IL para seu código:
Observe os blocos aninhados try / finally.
fonte
Este código (baseado na amostra original):
Ele produz o seguinte CIL (no Visual Studio 2013 , visando .NET 4.5.1):
Como você pode ver, o
try {}
bloco não começa antes da primeira alocação, que ocorre emIL_0012
. À primeira vista, isso parece alocar o primeiro item no código desprotegido. No entanto, observe que o resultado é armazenado na localização 0. Se a segunda alocação falhar, o bloco externo éfinally {}
executado e busca o objeto na localização 0, ou seja, a primeira alocação defont3
, e chama seuDispose()
método.Curiosamente, a descompilação desse conjunto com dotPeek produz a seguinte fonte reconstituída:
O código descompilado confirma que tudo está correto e que o
using
é essencialmente expandido emusing
s aninhados . O código CIL é um pouco confuso de se olhar, e eu tive que ficar olhando para ele por alguns minutos antes de entender corretamente o que estava acontecendo, então não estou surpreso que alguns 'contos de esposas antigas' começaram a surgir sobre isto. No entanto, o código gerado é a verdade inatacável.fonte
Aqui está um código de amostra para provar a resposta do @SLaks:
fonte
font4 = new Font
lançar? Pelo que entendi, o font3 vazará recursos e não será descartado."