Cosa succede veramente in un tentativo {return x; } infine {x = null; } dichiarazione?


259

Ho visto questo suggerimento in un'altra domanda e mi chiedevo se qualcuno potesse spiegarmi come mai funziona?

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

Voglio dire, la finallyclausola viene effettivamente eseguita dopo l' returnaffermazione? Quanto è sicuro questo thread? Riesci a pensare a qualsiasi altro hackery che può essere fatto contro questo try-finallyhack?

Risposte:


235

No: a livello di IL non è possibile tornare dall'interno di un blocco gestito da eccezioni. In sostanza lo memorizza in una variabile e ritorna in seguito

cioè simile a:

int tmp;
try {
  tmp = ...
} finally {
  ...
}
return tmp;

per esempio (usando il riflettore):

static int Test() {
    try {
        return SomeNumber();
    } finally {
        Foo();
    }
}

compila per:

.method private hidebysig static int32 Test() cil managed
{
    .maxstack 1
    .locals init (
        [0] int32 CS$1$0000)
    L_0000: call int32 Program::SomeNumber()
    L_0005: stloc.0 
    L_0006: leave.s L_000e
    L_0008: call void Program::Foo()
    L_000d: endfinally 
    L_000e: ldloc.0 
    L_000f: ret 
    .try L_0000 to L_0008 finally handler L_0008 to L_000e
}

Questo in pratica dichiara una variabile locale ( CS$1$0000), posiziona il valore nella variabile (all'interno del blocco gestito), quindi dopo essere uscito dal blocco carica la variabile, quindi la restituisce. Il riflettore lo rende come:

private static int Test()
{
    int CS$1$0000;
    try
    {
        CS$1$0000 = SomeNumber();
    }
    finally
    {
        Foo();
    }
    return CS$1$0000;
}

10
Non è esattamente quello che ha detto ocdedio: finalmente viene eseguito dopo il calcolo del valore di ritorno e prima di tornare veramente dalla funzione ???
mmmmmmmm

"blocco gestito dalle eccezioni" Penso che questo scenario non abbia nulla a che fare con le eccezioni e la gestione delle eccezioni. Questo è il modo in cui .NET implementa il costrutto di protezione delle risorse .
g.pickardou,

361

L'istruzione finally viene eseguita, ma il valore restituito non è interessato. L'ordine di esecuzione è:

  1. Codice prima dell'esecuzione dell'istruzione return
  2. Viene valutata l'espressione nella dichiarazione di ritorno
  3. infine viene eseguito il blocco
  4. Il risultato valutato nel passaggio 2 viene restituito

Ecco un breve programma per dimostrare:

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";
        }
    }
}

Questo stampa "prova" (perché è quello che viene restituito) e poi "finalmente" perché è il nuovo valore di x.

Naturalmente, se stiamo restituendo un riferimento a un oggetto mutabile (ad es. StringBuilder), tutte le modifiche apportate all'oggetto nel blocco finally saranno visibili al ritorno - questo non ha influito sul valore di ritorno stesso (che è solo un riferimento).


Vorrei chiedere, c'è qualche opzione in Visual Studio per vedere il linguaggio Intermedio (IL) generato per il codice C # scritto al momento dell'esecuzione ....
Enigma State

L'eccezione a "se stiamo restituendo un riferimento a un oggetto mutabile (ad esempio StringBuilder), tutte le modifiche apportate all'oggetto nel blocco finally saranno visibili al ritorno" è se l'oggetto StringBuilder è impostato su null nel blocco finally, nel qual caso viene restituito un oggetto non nullo.
Nick,

4
@Nick: Questa non è una modifica all'oggetto - è una modifica alla variabile . Non influisce affatto sull'oggetto a cui faceva riferimento il valore precedente della variabile. Quindi no, non fa eccezione.
Jon Skeet,

3
@Skeet Significa "l'istruzione return restituisce una copia"?
prabhakaran,

4
@prabhakaran: Beh, valuta l'espressione nel punto dell'istruzione returne quel valore verrà restituito. L'espressione non viene valutata poiché il controllo lascia il metodo.
Jon Skeet,

19

La clausola finally viene eseguita dopo l'istruzione return ma prima di tornare effettivamente dalla funzione. Ha poco a che fare con la sicurezza del thread, credo. Non è un hack: finalmente è garantito che funzionerà sempre indipendentemente da ciò che fai nel blocco try o nel blocco catch.


13

Aggiungendo alle risposte fornite da Marc Gravell e Jon Skeet, è importante notare che gli oggetti e altri tipi di riferimento si comportano in modo simile quando vengono restituiti, ma presentano alcune differenze.

Il "Cosa" che viene restituito segue la stessa logica dei tipi semplici:

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

Il riferimento che viene restituito è già stato valutato prima che alla variabile locale venga assegnato un nuovo riferimento nel blocco finally.

L'esecuzione è essenzialmente:

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

La differenza è che sarebbe ancora possibile modificare i tipi mutabili usando le proprietà / i metodi dell'oggetto che possono provocare comportamenti imprevisti se non si presta attenzione.

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

Una seconda cosa da considerare su try-return-finally è che i parametri passati "per riferimento" possono ancora essere modificati dopo il ritorno. È stato valutato solo il valore restituito ed è memorizzato in una variabile temporanea in attesa di essere restituito, eventuali altre variabili vengono comunque modificate nel modo normale. Il contratto di un parametro out può anche non essere completato fino a quando non si blocca in questo modo.

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

Come qualsiasi altro costrutto di flusso "try-return-finally" ha il suo posto e può consentire un codice più pulito rispetto alla scrittura della struttura in cui si compila effettivamente . Ma deve essere usato con attenzione per evitare i gotcha.


4

Se xè una variabile locale, non vedo il punto, poiché xsarà effettivamente impostato su null comunque quando il metodo viene abbandonato e il valore del valore restituito non è null (poiché è stato inserito nel registro prima della chiamata da impostare xnull).

Posso vedere che succede solo se vuoi garantire la variazione del valore di un campo al momento della restituzione (e dopo aver determinato il valore della restituzione).


A meno che anche la variabile locale non venga catturata da un delegato :)
Jon Skeet,

Quindi c'è una chiusura e l'oggetto non può essere garbage collection, perché c'è ancora un riferimento.
ricorsivo

Ma ancora non vedo perché useresti una var locale in un delegato a meno che tu non abbia intenzione di usare il suo valore.
ricorsivo
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.