when - working with exceptions c#




O que realmente acontece em uma tentativa{return x;}finalmente{x=null;}declaração? (4)

A cláusula finally é executada após a instrução de retorno, mas antes de retornar realmente da função. Tem pouco a ver com segurança de thread, eu acho. Não é um hack - o finalmente é garantido para sempre rodar, não importa o que você faça no seu bloco try ou no seu bloco catch.

Eu vi esta dica em outra pergunta e queria saber se alguém poderia me explicar como na terra isso funciona?

try { return x; } finally { x = null; }

Quero dizer, a cláusula finally realmente é executada após a declaração de return ? Quão inseguro é este código? Você pode pensar em qualquer hacker adicional que possa ser feito com essa try-finally hackear?


A instrução finally é executada, mas o valor de retorno não é afetado. A ordem de execução é:

  1. Código antes de declaração de retorno ser executada
  2. Expressão na declaração de retorno é avaliada
  3. finalmente o bloco é executado
  4. Resultado avaliado na etapa 2 é retornado

Aqui está um pequeno programa para demonstrar:

using System;

class Test
{
    static string x;

    static void Main()
    {
        Console.WriteLine(Method());
        Console.WriteLine(x);
    }

    static string Method()
    {
        try
        {
            x = "try";
            return x;
        }
        finally
        {
            x = "finally";
        }
    }
}

Isso imprime "try" (porque é o que é retornado) e depois "finally" porque esse é o novo valor de x.

Claro, se estamos retornando uma referência a um objeto mutável (por exemplo, um StringBuilder), então qualquer alteração feita no objeto no bloco finally será visível no retorno - isso não afetou o valor de retorno em si (que é apenas um referência).


Se x for uma variável local, não vejo o ponto, já que x será efetivamente definido como null quando o método for encerrado e o valor do valor de retorno não for nulo (desde que foi colocado no registrador antes da chamada para definir x como nulo).

Eu só posso ver isso acontecendo se você quiser garantir a alteração do valor de um campo após o retorno (e após o valor de retorno ser determinado).


Somando-se às respostas dadas por Marc Gravell e Jon Skeet, é importante observar que objetos e outros tipos de referência se comportam de maneira semelhante quando retornados, mas têm algumas diferenças.

O "What" que é retornado segue a mesma lógica dos tipos simples:

class Test {
    public static Exception AnException() {
        Exception ex = new Exception("Me");
        try {
            return ex;
        } finally {
            // Reference unchanged, Local variable changed
            ex = new Exception("Not Me");
        }
    }
}

A referência que está sendo retornada já foi avaliada antes que a variável local seja designada a uma nova referência no bloco finally.

A execução é essencialmente:

class Test {
    public static Exception AnException() {
        Exception ex = new Exception("Me");
        Exception CS$1$0000 = null;
        try {
            CS$1$0000 = ex;
        } finally {
            // Reference unchanged, Local variable changed
            ex = new Exception("Not Me");
        }
        return CS$1$0000;
    }
}

A diferença é que ainda seria possível modificar os tipos mutáveis ​​usando as propriedades / métodos do objeto, o que pode resultar em comportamentos inesperados se você não for cuidadoso.

class Test2 {
    public static System.IO.MemoryStream BadStream(byte[] buffer) {
        System.IO.MemoryStream ms = new System.IO.MemoryStream(buffer);
        try {
            return ms;
        } finally {
            // Reference unchanged, Referenced Object changed
            ms.Dispose();
        }
    }
}

Uma segunda coisa a considerar sobre try-return-finally é que os parâmetros passados ​​"por referência" ainda podem ser modificados após o retorno. Apenas o valor de retorno foi avaliado e é armazenado em uma variável temporária esperando para ser retornada, quaisquer outras variáveis ​​ainda são modificadas da maneira normal. O contrato de um parâmetro de saída pode até não ser atendido até que o bloco final seja assim.

class ByRefTests {
    public static int One(out int i) {
        try {
            i = 1;
            return i;
        } finally {
            // Return value unchanged, Store new value referenced variable
            i = 1000;
        }
    }

    public static int Two(ref int i) {
        try {
            i = 2;
            return i;
        } finally {
            // Return value unchanged, Store new value referenced variable
            i = 2000;
        }
    }

    public static int Three(out int i) {
        try {
            return 3;
        } finally {
            // This is not a compile error!
            // Return value unchanged, Store new value referenced variable
            i = 3000;
        }
    }
}

Como qualquer outra construção de fluxo, "try-return-finally" tem seu lugar e pode permitir um código de aparência mais limpa do que escrever a estrutura que realmente compila. Mas deve ser usado com cuidado para evitar pegadinhas.





exception-handling