Além das 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 existem algumas diferenças.
O "O quê" 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 da variável local receber uma nova referência no bloco final.
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 tipos mutáveis usando as propriedades / métodos do objeto que podem resultar em comportamentos inesperados, se você não tomar cuidado.
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-finalmente é que os parâmetros passados "por referência" ainda podem ser modificados após o retorno. Somente o valor de retorno foi avaliado e é armazenado em uma variável temporária aguardando retorno, 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 cumprido até o bloqueio final dessa maneira.
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-finalmente" tem seu lugar e pode permitir um código com aparência mais limpa do que escrever a estrutura na qual ele realmente compila. Mas deve ser usado com cuidado para evitar pegadinhas.