recursos - using equivalent c#




Pode “usar” com mais de um recurso causar um vazamento de recursos? (4)

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 for font4 = new Font ? Pelo que entendi font3 vai vazar recursos e não será descartado.

  • Isso é verdade? (font4 não será eliminado)
  • Isso significa que using(... , ...) deve ser totalmente evitado em favor do uso aninhado?

Aqui está um exemplo de código para provar a resposta do @SLaks:

void Main()
{
    try
    {
        using (TestUsing t1 = new TestUsing("t1"), t2 = new TestUsing("t2"))
        {
        }
    }
    catch(Exception ex)
    {
        Console.WriteLine("catch");
    }
    finally
    {
        Console.WriteLine("done");
    }

    /* outputs

        Construct: t1
        Construct: t2
        Dispose: t1
        catch
        done

    */
}

public class TestUsing : IDisposable
{
    public string Name {get; set;}

    public TestUsing(string name)
    {
        Name = name;

        Console.WriteLine("Construct: " + Name);

        if (Name == "t2") throw new Exception();
    }

    public void Dispose()
    {
        Console.WriteLine("Dispose: " + Name);
    }
}

Como complemento da resposta do @SLaks, aqui está o IL para o seu código:

.method private hidebysig static 
    void Main (
        string[] args
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 74 (0x4a)
    .maxstack 2
    .entrypoint
    .locals init (
        [0] class [System.Drawing]System.Drawing.Font font3,
        [1] class [System.Drawing]System.Drawing.Font font4,
        [2] bool CS$4$0000
    )

    IL_0000: nop
    IL_0001: ldstr "Arial"
    IL_0006: ldc.r4 10
    IL_000b: newobj instance void [System.Drawing]System.Drawing.Font::.ctor(string, float32)
    IL_0010: stloc.0
    .try
    {
        IL_0011: ldstr "Arial"
        IL_0016: ldc.r4 10
        IL_001b: newobj instance void [System.Drawing]System.Drawing.Font::.ctor(string, float32)
        IL_0020: stloc.1
        .try
        {
            IL_0021: nop
            IL_0022: nop
            IL_0023: leave.s IL_0035
        } // end .try
        finally
        {
            IL_0025: ldloc.1
            IL_0026: ldnull
            IL_0027: ceq
            IL_0029: stloc.2
            IL_002a: ldloc.2
            IL_002b: brtrue.s IL_0034

            IL_002d: ldloc.1
            IL_002e: callvirt instance void [mscorlib]System.IDisposable::Dispose()
            IL_0033: nop

            IL_0034: endfinally
        } // end handler

        IL_0035: nop
        IL_0036: leave.s IL_0048
    } // end .try
    finally
    {
        IL_0038: ldloc.0
        IL_0039: ldnull
        IL_003a: ceq
        IL_003c: stloc.2
        IL_003d: ldloc.2
        IL_003e: brtrue.s IL_0047

        IL_0040: ldloc.0
        IL_0041: callvirt instance void [mscorlib]System.IDisposable::Dispose()
        IL_0046: nop

        IL_0047: endfinally
    } // end handler

    IL_0048: nop
    IL_0049: ret
} // end of method Program::Main

Observe os blocos aninhados try / finally.


Não.

O compilador irá gerar um bloco finally separado para cada variável.

A spec (§8.13) diz:

Quando uma aquisição de recursos toma a forma de uma declaração de variável local, é possível adquirir vários recursos de um determinado tipo. Uma declaração using o formulário

using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) statement 

é precisamente equivalente a uma sequência de instruções usando aninhadas:

using (ResourceType r1 = e1)
   using (ResourceType r2 = e2)
      ...
         using (ResourceType rN = eN)
            statement

ATUALIZAÇÃO : Eu usei essa questão como base para um artigo que pode ser encontrado here ; veja-o para uma discussão adicional sobre este assunto. Obrigado pela boa pergunta!

Embora a resposta de Schabse esteja correta e responda à pergunta que foi feita, há uma variante importante em sua pergunta que você não fez:

O que acontece se font4 = new Font() for font4 = new Font() após o recurso não gerenciado ser 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 tenhamos:

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);

Isso é o mesmo que

{
    Foo foo = new Foo();
    try
    {
        Whatever(foo);
    }
    finally
    {
        IDisposable d = foo as IDisposable;
        if (d != null) 
            d.Dispose();
    }
}

ESTÁ BEM. Suponha o que Whatever que Whatever lançado. Em seguida, o bloco finally é 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 driver nunca retorna, então foo nunca é preenchido. Nós nunca entramos na try então nunca entramos no finally . A referência do objeto foi ó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 em face de um objeto que está sendo finalizado cujo construtor nunca foi concluído . Você é obrigado a escrever finalizadores que sejam tão fortes. Esta é mais uma razão pela qual você deve deixar de escrever finalizadores para especialistas e não tentar fazer isso sozinho.

Suponha que Blah3() jogue. O lançamento acontece depois que o recurso é alocado. Mas, novamente, foo nunca é preenchido, nunca entramos no finally , e o objeto é limpo pelo thread 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, deve, porque desta vez, tinha trabalho a fazer.

Agora suponha que Blah2() jogue. O lançamento acontece depois que o recurso é alocado, mas antes que o handle seja preenchido! Mais uma vez, o finalizador será executado, mas agora o handle ainda é zero e vazamos a alça!

Você precisa escrever um código extremamente inteligente para evitar que esse vazamento aconteça. Agora, no caso do seu recurso de Font , quem diabos se importa? Nós vazamos um identificador de fonte, grande coisa. Mas se você absolutamente positivamente exigir que todo recurso não gerenciado seja limpo, não importa qual seja o tempo das exceções, então você tem um problema muito difícil em suas mãos.

O CLR tem que resolver este problema com bloqueios. Desde o C # 4, os bloqueios que usam a instrução de lock foram implementados da seguinte forma:

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 , o lockEntered seja configurado como true se, e somente se, o bloqueio for realmente executado. Se você tem requisitos semelhantes, então o que você precisa é realmente escrever:

    public Foo()
    {
        Blah1();
        AllocateResource(ref handle);
        Blah2();
        Blah3();
    }

e escreva AllocateResource inteligente, como o Monitor.Enter para que, independentemente do que aconteça no interior de AllocateResource , o handle seja preenchido se, e somente se, ele 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.





using-statement