Perché non sono le variabili dichiarate in “prova” in ambito “cattura” o “finalmente”?

StackOverflow https://stackoverflow.com/questions/94977

Domanda

In C# e Java (e possibilmente di altre lingue), le variabili dichiarate in una "prova" di blocco non sono nell'ambito del corrispondente "cattura" o "finalmente" blocchi.Per esempio, il seguente codice non compila:

try {
  String s = "test";
  // (more code...)
}
catch {
  Console.Out.WriteLine(s);  //Java fans: think "System.out.println" here instead
}

In questo codice, un errore in fase di compilazione si verifica in riferimento alla s nel blocco catch, perché è solo nel campo di applicazione del blocco try.(In Java, l'errore di compilazione è "s non può essere risolto";in C#, è "Il nome 's' non esiste nel contesto corrente".)

La soluzione a questo problema sembra essere, invece di dichiarare le variabili appena prima del blocco try, anziché all'interno di un blocco try:

String s;
try {
  s = "test";
  // (more code...)
}
catch {
  Console.Out.WriteLine(s);  //Java fans: think "System.out.println" here instead
}

Tuttavia, almeno per me, (1) questo si sente come un goffo soluzione, e (2) si traduce in variabili di avere una portata più grande programmatore inteso (tutto il resto del metodo, invece che solo nel contesto di un try-catch-finally).

La mia domanda è, che cosa erano/sono le motivazioni alla base di questa lingua decisione di progettazione (in Java, in C# e/o in altre lingue applicabile)?

È stato utile?

Soluzione

Due cose:

  1. In generale, Java ha solo 2 livelli di portata:globale e la funzione.Ma, try/catch è un'eccezione (no pun intended).Quando viene generata un'eccezione e l'eccezione oggetto si ottiene una variabile assegnata, che la variabile oggetto è disponibile solo all'interno di "catch" e viene distrutto non appena la cattura completa.

  2. (e più importante).Non è possibile sapere dove nel blocco try viene generata l'eccezione.Potrebbe essere stato prima variabile è stata dichiarata.Quindi è impossibile dire ciò che le variabili che saranno disponibili per il catch/finally clausola.Si consideri il seguente caso, dove scoping è come hai suggerito:

    
    try
    {
        throw new ArgumentException("some operation that throws an exception");
        string s = "blah";
    }
    catch (e as ArgumentException)
    {  
        Console.Out.WriteLine(s);
    }
    

Questo è chiaramente un problema quando si raggiunge il gestore di eccezioni, s non sono stati dichiarati.Dato che le catture sono pensati per gestire circostanze eccezionali e finallys deve eseguire, essere sicuro e dichiarando questo è un problema in fase di compilazione è molto meglio che in fase di runtime.

Altri suggerimenti

Come si poteva essere certi, che ha raggiunto la parte di dichiarazione nel blocco catch?Che cosa succede se l'istanza genera l'eccezione?

Tradizionalmente, in stile C lingue, cosa succede all'interno delle parentesi graffe rimane all'interno delle parentesi graffe.Penso che avere la durata di una variabile tratto tra ambiti come quello sarebbe poco intuitivo per la maggior parte dei programmatori.È possibile ottenere ciò che si vuole racchiudere il try/catch/finally blocchi all'interno di un altro livello di parentesi graffe.ad es.

... code ...
{
    string s = "test";
    try
    {
        // more code
    }
    catch(...)
    {
        Console.Out.WriteLine(s);
    }
}

EDIT:Credo che ogni regola non sono un'eccezione.Il seguente è valido C++:

int f() { return 0; }

void main() 
{
    int y = 0;

    if (int x = f())
    {
        cout << x;
    }
    else
    {
        cout << x;
    }
}

Il campo di applicazione x è il condizionale, allora la clausola e la clausola else.

Ognuno di noi ha portato le nozioni di base-cosa succede in un blocco rimane in un blocco.Ma nel caso di .Rete, può essere utile esaminare ciò che il compilatore pensa che sta accadendo.Prendiamo, per esempio, il seguente try/catch codice (nota che StreamReader è dichiarato, correttamente, al di fuori di blocchi):

static void TryCatchFinally()
{
    StreamReader sr = null;
    try
    {
        sr = new StreamReader(path);
        Console.WriteLine(sr.ReadToEnd());
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
    finally
    {
        if (sr != null)
        {
            sr.Close();
        }
    }
}

Questo compilerà qualcosa di simile al seguente nel MSIL:

.method private hidebysig static void  TryCatchFinallyDispose() cil managed
{
  // Code size       53 (0x35)    
  .maxstack  2    
  .locals init ([0] class [mscorlib]System.IO.StreamReader sr,    
           [1] class [mscorlib]System.Exception ex)    
  IL_0000:  ldnull    
  IL_0001:  stloc.0    
  .try    
  {    
    .try    
    {    
      IL_0002:  ldsfld     string UsingTest.Class1::path    
      IL_0007:  newobj     instance void [mscorlib]System.IO.StreamReader::.ctor(string)    
      IL_000c:  stloc.0    
      IL_000d:  ldloc.0    
      IL_000e:  callvirt   instance string [mscorlib]System.IO.TextReader::ReadToEnd()
      IL_0013:  call       void [mscorlib]System.Console::WriteLine(string)    
      IL_0018:  leave.s    IL_0028
    }  // end .try
    catch [mscorlib]System.Exception 
    {
      IL_001a:  stloc.1
      IL_001b:  ldloc.1    
      IL_001c:  callvirt   instance string [mscorlib]System.Exception::ToString()    
      IL_0021:  call       void [mscorlib]System.Console::WriteLine(string)    
      IL_0026:  leave.s    IL_0028    
    }  // end handler    
    IL_0028:  leave.s    IL_0034    
  }  // end .try    
  finally    
  {    
    IL_002a:  ldloc.0    
    IL_002b:  brfalse.s  IL_0033    
    IL_002d:  ldloc.0    
    IL_002e:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()    
    IL_0033:  endfinally    
  }  // end handler    
  IL_0034:  ret    
} // end of method Class1::TryCatchFinallyDispose

Che cosa vediamo?MSIL rispetta i blocchi-sono intrinsecamente parte del sottostante il codice generato quando si compila il C#.L'ambito non è solo difficile-set in C# specifiche, è il CLR e CLS spec così.

Il campo di applicazione protegge, ma di tanto in tanto hanno di lavorare intorno ad esso.Nel corso del tempo, ci si abitua ad esso, e si comincia a sentire naturale.Come tutti gli altri ha detto, ciò che accade in un blocco rimane in blocco.Si desidera condividere qualcosa?Devi andare fuori i blocchi ...

In C++ in ogni caso, l'ambito di applicazione di una variabile automatica è limitata dalla parentesi graffe che la circondano.Perché qualcuno dovrebbe pensare che questo essere diversi da plunking giù un tentativo di parole chiave al di fuori delle parentesi graffe?

Come ravenspoint sottolineato, tutti si aspettano che le variabili locali al blocco sono definiti. try introduce un blocco e quindi non catch.

Se si desidera che le variabili locali ad entrambi try e catch, provare che racchiude sia in blocco:

// here is some code
{
    string s;
    try
    {

        throw new Exception(":(")
    }
    catch (Exception e)
    {
        Debug.WriteLine(s);
    }
}

La risposta semplice è che C e la maggior parte delle lingue che hanno ereditato la sua sintassi in blocco ambito.Ciò significa che se una variabile è definita in un unico blocco, cioè, all'interno di { }, che è il suo ambito di applicazione.

L'eccezione, a proposito, è un JavaScript, che ha una sintassi simile, ma è funzione di ambito.In JavaScript, una variabile dichiarata in un blocco try viene portata nel blocco catch, e tutto il resto nella sua funzione di contenimento.

@burkhard è la domanda di come mai risposto correttamente, ma come una nota che ho voluto aggiungere, mentre la tua soluzione consigliata esempio è buona 99.9999+% del tempo, non è una buona pratica, è molto più sicuro per controllare null prima di usare qualcosa creare un'istanza all'interno del blocco try, o inizializzare la variabile a qualcosa invece di dichiarare prima il blocco try.Per esempio:

string s = String.Empty;
try
{
    //do work
}
catch
{
   //safely access s
   Console.WriteLine(s);
}

O:

string s;
try
{
    //do work
}
catch
{
   if (!String.IsNullOrEmpty(s))
   {
       //safely access s
       Console.WriteLine(s);
   }
}

Questo dovrebbe fornire la scalabilità della soluzione, in modo che anche quando ciò che si sta facendo nel blocco try è più complessa rispetto all'assegnazione di una stringa, si dovrebbe essere in grado di accedere in modo sicuro i dati da un blocco catch.

La risposta, come tutti hanno sottolineato, è praticamente "ecco come blocchi vengono definiti".

Ci sono alcune proposte per rendere il codice più bella.Vedere BRACCIO

 try (FileReader in = makeReader(), FileWriter out = makeWriter()) {
       // code using in and out
 } catch(IOException e) {
       // ...
 }

Chiusure si suppone che l'indirizzo di questo.

with(FileReader in : makeReader()) with(FileWriter out : makeWriter()) {
    // code using in and out
}

AGGIORNAMENTO: Il BRACCIO è implementato in Java 7. http://download.java.net/jdk7/docs/technotes/guides/language/try-with-resources.html

Secondo la sezione intitolata "Come il lancio e la Cattura di Eccezioni" nella Lezione 2 di MCTS Self-Paced Training Kit (Esame 70-536):Microsoft® .NET Framework 2.0—Application Development Foundation, la ragione è che l'eccezione può essersi verificato prima le dichiarazioni di variabili nel blocco try (come altri hanno già sottolineato).

Citazione da pagina 25:

"Si noti che il StreamReader dichiarazione è stata spostata al di fuori del blocco Try nell'esempio precedente.Questo è necessario perché il blocco Finally, non possono accedere le variabili dichiarate all'interno di un blocco Try. Questo ha senso perché a seconda di dove si è verificata un'eccezione, le dichiarazioni di variabili all'interno del blocco Try potrebbe non essere stata ancora eseguita."

Si la soluzione è esattamente ciò che si dovrebbe fare.Non si può essere sicuri che la vostra dichiarazione è stato anche raggiunto nel blocco try, che si tradurrà in un'altra eccezione nel blocco catch.

Si deve semplicemente lavorare come distinti ambiti.

try
    dim i as integer = 10 / 0 ''// Throw an exception
    dim s as string = "hi"
catch (e)
    console.writeln(s) ''// Would throw another exception, if this was allowed to compile
end try

Le variabili sono a livello di blocco e limitato a quello di Provare o di blocco Catch.Simile alla definizione di una variabile in un'istruzione if.Pensare a questa situazione.

try {    
    fileOpen("no real file Name");    
    String s = "GO TROJANS"; 
} catch (Exception) {   
    print(s); 
}

La Stringa dovrebbe mai essere dichiarato, così non può essere dipendeva.

A causa di un blocco try e catch block sono 2 blocchi diversi.

Nel codice seguente, che ci si aspetta da s, ha definito il blocco di Un essere visibile nel blocco B?

{ // block A
  string s = "dude";
}

{ // block B
  Console.Out.WriteLine(s); // or printf or whatever
}

Nello specifico esempio che hai dato, inizializzazione s non può lanciare un'eccezione.Così si potrebbe pensare che, forse, il suo ambito di applicazione potrebbe essere esteso.

Ma in generale, initialiser espressioni possono generare eccezioni.Non avrebbe senso per una variabile il cui initialiser ha generato un'eccezione (o di cui è stato dichiarato dopo un'altra variabile dove che è successo) di essere portata per il catch/finally.

Inoltre, la leggibilità del codice avrebbe sofferto.La regola in C (e le lingue che la seguono, tra cui C++, Java e C#) è semplice:variabile ambiti di seguire blocchi.

Se si desidera che una variabile sia in ambito per try/catch/finally, ma in nessun altro luogo, poi avvolgere il tutto in un altro set di parentesi graffe (un nudo di blocco) e dichiarare la variabile prima di provare.

Parte il motivo per cui non sono nello stesso ambito è perché in qualsiasi punto del blocco try, si può avere gettato l'eccezione.Se fossero nello stesso ambito, è un disastro in attesa, perché a seconda di dove è stata lanciata l'eccezione, potrebbe essere anche più ambiguo.

Almeno quando la sua dichiarata al di fuori del blocco try, si sa per certo che il variabile al minimo potrebbe essere quando viene generata un'eccezione;Il valore della variabile prima del blocco try.

Quando si dichiara una variabile locale viene inserito nello stack (per alcuni tipi l'intero valore dell'oggetto in pila, per altri tipi solo un riferimento sullo stack).Quando c'è un'eccezione all'interno di un blocco try, le variabili locali all'interno del blocco sono liberato, il che significa che la pila è "svuotato" torna allo stato in cui si era all'inizio di un blocco try.Questo è il design.E ' come il try / catch è in grado di eseguire fuori di tutte le chiamate di funzione all'interno di un blocco e mette il sistema in uno stato funzionale.Senza questo meccanismo non si poteva mai essere sicuri di stato di tutto ciò, quando si verifica un'eccezione.

Avere il vostro codice di gestione degli errori contare su esternamente dichiarato le variabili che sono i valori modificati all'interno del blocco try sembra male come design a me.Quello che stai facendo è essenzialmente perdite di risorse, intenzionalmente, al fine di ottenere informazioni (in questo particolare caso non è così male, perché si sono solo perdite di informazioni, ma immaginate se fosse qualche altra risorsa?stai solo rendendo la vita più difficile su te stesso in futuro).Vorrei suggerire di rompere il blocco try in blocchi più piccoli se avete bisogno di più granularità nella gestione degli errori.

Quando si dispone di un try catch, si deve in gran parte a sapere che gli errori che si potrebbero generare.Queste classi di Eccezione normalmente raccontare tutto il necessario sull'eccezione.Se non, si dovrebbe fare la propria classi di eccezione e passare le informazioni lungo.In quel modo, non sarà necessario per ottenere le variabili all'interno del blocco try, perché l'Eccezione è di per sé explainatory.Quindi, se avete bisogno di fare questo un sacco, pensa che stai design, e prova a pensare se c'è qualche altro modo, che si può prevedere eccezioni arrivando, o utilizzare le informazioni che provengono dalla eccezioni, e poi magari rigenerare il proprio eccezione, con più informazioni.

Come è stato sottolineato da altri utenti, le parentesi graffe definire l'ambito in praticamente ogni C linguaggio di stile, che io sappia.

Se è una semplice variabile, allora perché ti preoccupi di quanto tempo sarà portata?Non è un grande affare.

in C#, se si tratta di una variabile complessa, si vuole implementare IDisposable.È quindi possibile utilizzare il try/catch/finally e chiamare obj.Dispose() nel blocco finally.Oppure è possibile utilizzare la parola chiave, che verrà automaticamente chiamare Dispose alla fine della sezione di codice.

In Python, sono visibili nel catch/finally blocca se la linea che dichiarano di non buttare.

Che cosa succede se l'eccezione viene generata nel codice che è al di sopra della dichiarazione della variabile.Il che significa che, la dichiarazione di per sé non è successo in questo caso.

try {

       //doSomeWork // Exception is thrown in this line. 
       String s;
       //doRestOfTheWork

} catch (Exception) {
        //Use s;//Problem here
} finally {
        //Use s;//Problem here
}

Mentre nel tuo esempio è strano che non funziona, prendere questo uno simile:

    try
    {
         //Code 1
         String s = "1|2";
         //Code 2
    }
    catch
    {
         Console.WriteLine(s.Split('|')[1]);
    }

Questo potrebbe causare il fermo per lanciare una eccezione di riferimento null se il Codice 1 si è rotto.Ora, mentre la semantica di try/catch sono piuttosto ben capito, questo sarebbe un fastidioso caso d'angolo, dal momento che s è definito con un valore iniziale, quindi dovrebbe in teoria mai essere nullo, ma sotto condivisi, semantica, sarebbe.

Di nuovo, questo potrebbe in teoria essere fissi, consentendo solo separati definizioni (String s; s = "1|2";), o di qualche altra serie di condizioni, ma è generalmente più facile dire di no.

Inoltre, permette la semantica del campo di applicazione per essere definiti a livello globale, senza eccezioni, in particolare, i locali durare più a lungo il {} essi sono definiti, in tutti i casi.Punto minore, ma un punto.

Infine, per fare ciò che si desidera, è possibile aggiungere una serie di parentesi il try catch.Ti dà l'ambito si desidera, anche se non sono disponibili al costo di un po ' di leggibilità, ma non troppo.

{
     String s;
     try
     {
          s = "test";
          //More code
     }
     catch
     {
          Console.WriteLine(s);
     }
}

Il mio pensiero sarebbe che perché qualcosa nel blocco try ha causato l'eccezione proprio spazio dei nomi contenuti non può essere attendibile - ie riferimento alla Stringa " s " nel blocco catch, potrebbe essere causa di gettare ancora di un'eccezione.

Beh, se non buttare un errore di compilazione, ed è possibile dichiarare che per il resto del metodo, quindi non ci sarebbe alcun modo per dichiarare solo solo entro provare ambito.È ti costringe a essere esplicito, per cui la variabile si suppone che esista e non fare ipotesi.

Se si ignora l'scoping-problema di blocco per un momento, il compilatore avrebbe dovuto lavorare molto più difficile, in una situazione che non è ben definito.Mentre questo non è impossibile, l'ambito di errore, inoltre, le forze, l'autore del codice, per realizzare l'implicazione del codice scritto (che la stringa s può essere null nel blocco catch).Se il codice è legale, nel caso di un'eccezione OutOfMemory, s non è ancora garantito per essere assegnato uno slot di memoria:

// won't compile!
try
{
    VeryLargeArray v = new VeryLargeArray(TOO_BIG_CONSTANT); // throws OutOfMemoryException
    string s = "Help";
}
catch
{
    Console.WriteLine(s); // whoops!
}

Il CLR (e quindi il compilatore) anche la forza per inizializzare le variabili, prima di essere usati.Nel blocco catch presentato non può garantire questo.

Così si finisce con il compilatore di dover fare un sacco di lavoro, che in pratica non offre molto beneficio e probabilmente confondere la gente e li portano a chiedersi il perché di try/catch funziona in modo diverso.

Inoltre, per coerenza, non permettendo di nulla di fantasia e di aderire alla già consolidata di scoping semantica utilizzata in tutta la lingua, il compilatore e CLR sono in grado di fornire una maggiore garanzia di stato di una variabile all'interno di un blocco catch.Che esiste ed è stato inizializzato.

Nota che la lingua progettisti hanno fatto un buon lavoro con altri costrutti come utilizzando e blocco dove è il problema e la portata è ben definito, che consente di scrivere più chiaro del codice.

ad es.il utilizzando parola chiave con IDisposable oggetti:

using(Writer writer = new Writer())
{
    writer.Write("Hello");
}

è equivalente a:

Writer writer = new Writer();
try
{        
    writer.Write("Hello");
}
finally
{
    if( writer != null)
    {
        ((IDisposable)writer).Dispose();
    }
}

Se il try/catch/finally, è difficile da capire, provare refactoring o l'introduzione di un altro livello di riferimento indiretto con un intermedio in classe che incapsula la semantica di ciò che si sta cercando di realizzare.Senza vedere il codice vero e proprio, è difficile essere più precisi.

Invece di una variabile locale, una proprietà pubblica può essere dichiarato;questo dovrebbe anche evitare un altro potenziale errore di variabile non assegnata.public string S { get;set;}

Il C# Spec (15.2) afferma che "L'ambito di applicazione di una variabile locale o costante dichiarata in un blocco è il blocco".

(nel primo esempio il blocco try è il blocco, dove "s" è dichiarata)

Se l'assegnazione esito negativo dell'operazione di cattura istruzione avrà un riferimento null indietro nessun variabile.

C# 3.0:

string html = new Func<string>(() =>
{
    string webpage;

    try
    {
        using(WebClient downloader = new WebClient())
        {
            webpage = downloader.DownloadString(url);
        }
    }
    catch(WebException)
    {
        Console.WriteLine("Download failed.");  
    }

    return webpage;
})();
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top