Question

En C # et en Java (et éventuellement dans d'autres langages), les variables déclarées dans un " try " ne sont pas concernés par les "captures" correspondantes. ou "finalement" des blocs. Par exemple, le code suivant ne compile pas:

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

Dans ce code, une erreur de compilation se produit lors de la référence à s dans le bloc catch, car s est uniquement dans la portée du bloc try. (En Java, l’erreur de compilation est "s ne peut pas être résolue"; en C #, c’est "Le nom n’existe pas dans le contexte actuel".)

La solution générale à ce problème semble être de déclarer les variables juste avant le bloc try, plutôt que dans le bloc try:

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

Cependant, du moins pour moi, (1) cela ressemble à une solution maladroite, et (2) il en résulte que les variables ont une portée plus grande que celle prévue par le programmeur (tout le reste de la méthode, au lieu contexte du try-catch-finally).

Ma question est la suivante: quelles sont les raisons qui ont motivé cette décision de conception de langage (en Java, en C # et / ou dans tout autre langage applicable)?

Était-ce utile?

La solution

Deux choses:

  1. En général, Java n’a que 2 niveaux de portée: global et fonction. Mais, essayer / attraper est une exception (sans jeu de mots). Lorsqu'une exception est levée et que l'objet exception reçoit une variable qui lui est affectée, cette variable d'objet n'est disponible que dans la zone "catch". section et est détruite dès que la capture est terminée.

  2. (et plus important encore). Vous ne pouvez pas savoir où l'exception a été lancée dans le bloc try. C'était peut-être avant que votre variable soit déclarée. Il est donc impossible de dire quelles variables seront disponibles pour la clause catch / finally. Considérez le cas suivant où la portée est celle que vous avez suggérée:

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

C’est clairement un problème - lorsque vous atteignez le gestionnaire d’exceptions, s n’a pas été déclaré. Étant donné que les captures sont conçues pour faire face à des circonstances exceptionnelles et que doit impérativement être exécuté, il est préférable de déclarer qu'il s'agit d'un problème au moment de la compilation, ce qui est bien mieux qu'au moment de l'exécution.

Autres conseils

Comment pouvez-vous être sûr que vous avez atteint la partie déclaration de votre bloc catch? Et si l'instanciation lève l'exception?

Traditionnellement, dans les langages de style C, ce qui se passe à l'intérieur des accolades reste à l'intérieur des accolades. Je pense qu'avoir une durée de vie variable à travers des domaines tels que celui-ci ne serait pas intuitif pour la plupart des programmeurs. Vous pouvez obtenir ce que vous voulez en enfermant les blocs try / catch / finally dans un autre niveau d'accolades. par exemple

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

EDIT: Je suppose que chaque règle a une exception. Ce qui suit est valide C ++:

int f() { return 0; }

void main() 
{
    int y = 0;

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

La portée de x est le conditionnel, la clause then et la clause else.

Tout le monde a abordé les bases - ce qui se passe dans un bloc reste dans un bloc. Mais dans le cas de .NET, il peut être utile d’examiner ce que le compilateur pense être en train de se passer. Prenez, par exemple, le code try / catch suivant (notez que le StreamReader est déclaré, correctement, en dehors des blocs):

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

Cela compilera quelque chose de similaire à ce qui suit dans 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

Que voit-on? MSIL respecte les blocs - ils font intrinsèquement partie du code sous-jacent généré lors de la compilation de votre C #. La portée n'est pas simplement définie dans la spécification C #, elle l'est également dans les spécifications CLR et CLS.

Le scope vous protège, mais vous devez parfois le contourner. Au fil du temps, vous vous y habituez et cela commence à se sentir naturel. Comme tout le monde le dit, ce qui se passe dans un bloc reste dans ce bloc. Tu veux partager quelque chose? Vous devez sortir des blocs ...

En C ++, la portée d’une variable automatique est limitée par les accolades qui l’entourent. Pourquoi une personne s’attendrait-elle à ce que ce soit différent en utilisant un mot-clé try en dehors des accolades?

Comme Ravenspoint l'a souligné, tout le monde s'attend à ce que les variables soient locales au bloc dans lequel elles sont définies. try introduit un bloc, de même que capture .

Si vous voulez des variables locales à la fois à essayez et à catch , essayez de les inclure dans un bloc:

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

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

La réponse simple est que C et la plupart des langages qui ont hérité de sa syntaxe ont une portée de bloc. Cela signifie que si une variable est définie dans un bloc, c'est-à-dire à l'intérieur de {}, c'est sa portée.

L’exception, en passant, est JavaScript, qui a une syntaxe similaire, mais dont la portée est fonction. En JavaScript, une variable déclarée dans un bloc try est incluse dans la portée du bloc catch et partout ailleurs dans la fonction qui la contient.

@burkhard a demandé pourquoi il avait répondu correctement, mais comme remarque que je voulais ajouter, bien que votre exemple de solution recommandé soit correct 99,9999 +% du temps, ce n'est pas une bonne pratique, il est beaucoup plus sûr de vérifier null avant d'utiliser quelque chose instancie dans le bloc try ou initialisez la variable à quelque chose au lieu de simplement la déclarer avant le bloc try. Par exemple:

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

Ou:

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

Cela devrait permettre l'évolutivité de la solution de contournement. Ainsi, même si ce que vous faites dans le bloc try est plus complexe que l'affectation d'une chaîne, vous devriez pouvoir accéder en toute sécurité aux données de votre bloc catch.

Comme tout le monde l’a souligné, la réponse est quasiment "c’est ainsi que sont définis les blocs".

Il y a quelques propositions pour rendre le code plus joli. Voir ARM

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

fermetures sont censées y remédier également.

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

UPDATE: ARM est implémenté dans Java 7. http://download.java.net/jdk7/docs/technotes/guides/language/try-with-resources.html

Selon la section "Comment lancer et capturer des exceptions" dans la leçon 2 du kit de formation auto-rythmée des SCTM (Examen 70-536): Microsoft® .NET Framework 2.0 - Application Development Foundation , la raison en est que l'exception a peut-être eu lieu avant les déclarations de variables dans l'essai bloquer (comme d'autres l'ont déjà noté).

Extrait de la page 25:

"Notez que la déclaration StreamReader a été déplacée en dehors du bloc Try dans l'exemple précédent. Cela est nécessaire car le bloc Finally ne peut pas accéder aux variables déclarées dans le bloc Try. Cela a du sens, car, selon l'endroit où une exception s'est produite, les déclarations de variable du bloc Try n'ont peut-être pas encore été exécutées . "

Votre solution est exactement ce que vous devez faire. Vous ne pouvez pas être sûr que votre déclaration a même été atteinte dans le bloc try, ce qui entraînerait une autre exception dans le bloc catch.

Cela doit simplement fonctionner comme des portées séparées.

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

Les variables sont au niveau du bloc et limitées à ce bloc Try ou Catch. Semblable à la définition d'une variable dans une instruction if. Pensez à cette situation.

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

La chaîne ne serait jamais déclarée, elle ne peut donc pas être utilisée.

Parce que le bloc try et le bloc catch sont deux blocs différents.

Dans le code suivant, vous attendez-vous à ce que s défini dans le bloc A soit visible dans le bloc B?

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

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

Dans l'exemple spécifique que vous avez donné, l'initialisation de s ne peut pas lever une exception. On pourrait donc penser que son champ d'application pourrait être étendu.

Mais en général, les expressions d'initialisation peuvent générer des exceptions. Cela n’aurait aucun sens pour une variable dont l’initialiseur a généré une exception (ou qui a été déclarée après une autre variable où cela s’est produit) d’être dans la portée de catch / finally.

De plus, la lisibilité du code en souffrirait. La règle en C (et les langages qui le suivent, y compris C ++, Java et C #) est simple: les étendues de variables suivent des blocs.

Si vous voulez qu'une variable soit à portée de main pour try / catch / finally mais nulle part ailleurs, enveloppez le tout dans un autre jeu d'accolades (un bloc nu) et déclarez la variable avant l'essai.

Une partie de la raison pour laquelle ils ne sont pas dans la même portée est qu’à tout moment du bloc try, vous pouvez avoir levé l’exception. S'ils étaient dans la même étendue, c'est une catastrophe en attente, car selon l'endroit où l'exception a été lancée, cela pourrait être encore plus ambigu.

Au moins quand il est déclaré en dehors du bloc try, vous savez avec certitude quelle pourrait être la variable minimale lorsqu'une exception est levée; La valeur de la variable avant le bloc try.

Lorsque vous déclarez une variable locale, elle est placée dans la pile (pour certains types, la valeur entière de l'objet sera dans la pile, pour d'autres types, une référence seulement sera dans la pile). Lorsqu'il y a une exception à l'intérieur d'un bloc try, les variables locales du bloc sont libérées, ce qui signifie que la pile est "déroulée". retour à l'état où il était au début du bloc try. C'est par conception. C'est ainsi que try / catch est capable de sortir de tous les appels de fonction du bloc et de remettre votre système dans un état fonctionnel. Sans ce mécanisme, vous ne pouvez jamais être sûr de l'état de quoi que ce soit lorsqu'une exception se produit.

Faire en sorte que votre code de traitement des erreurs repose sur des variables déclarées en externe dont les valeurs ont été modifiées à l'intérieur du bloc try me semble mal conçu. Ce que vous faites, c’est essentiellement une fuite intentionnelle de ressources dans le but d’obtenir des informations (dans ce cas particulier, ce n’est pas si grave parce que vous ne faites que divulguer des informations, mais imaginez si c’était une autre ressource? Vous ne faites que vous rendre la vie plus difficile. futur). Je suggérerais de diviser vos blocs d’essai en morceaux plus petits si vous avez besoin de plus de précision dans la gestion des erreurs.

Lorsque vous avez un problème d’essai, vous devez tout au plus savoir quelles erreurs il peut générer. Les classes d'exception Theese disent normalement tout ce dont vous avez besoin à propos de l'exception. Sinon, vous devriez créer vos propres classes d'exceptions et transmettre ces informations. De cette façon, vous n'aurez jamais besoin de récupérer les variables à l'intérieur du bloc try, car l'exception est auto-explicative. Donc, si vous devez faire cela beaucoup, pensez à votre conception, et essayez de penser s'il y a un autre moyen, que vous pouvez prédire les exceptions à venir, ou utiliser les informations provenant des exceptions, et ensuite peut-être réémettre vos propres exception avec plus d'informations.

Comme d'autres utilisateurs l'ont signalé, les accolades définissent la portée de pratiquement tous les langages de style C que je connais.

S'il s'agit d'une simple variable, alors pourquoi vous souciez-vous de sa durée? Ce n'est pas un gros problème.

en C #, s’il s’agit d’une variable complexe, vous voudrez implémenter IDisposable. Vous pouvez ensuite utiliser try / catch / finally et appeler obj.Dispose () dans le bloc finally. Vous pouvez également utiliser le mot-clé using, qui appellera automatiquement Dispose à la fin de la section de code.

En Python, ils sont visibles dans les blocs catch / finally si la ligne les déclarant n'a pas été lancée.

Que se passe-t-il si l'exception est renvoyée dans un code qui se trouve au-dessus de la déclaration de la variable? Ce qui signifie que la déclaration elle-même n'a pas été effectuée dans ce cas.

try {

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

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

Bien que, dans votre exemple, il soit étrange que cela ne fonctionne pas, prenons celui-ci similaire:

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

Ceci entraînerait une exception de référence NULL si le code 1 était cassé. Maintenant, bien que la sémantique de try / catch soit bien comprise, il s’agirait d’un cas délicat, puisque s est défini avec une valeur initiale, il devrait donc en théorie ne jamais être nul, mais sous sémantique partagée, ce serait.

Encore une fois, cela pourrait théoriquement être corrigé en autorisant uniquement les définitions séparées ( String s; s = "1 | 2"; ), ou un autre ensemble de conditions, mais il est généralement plus facile de il suffit de dire non.

De plus, cela permet à la sémantique de scope d'être définie globalement sans exception, en particulier les locals durent aussi longtemps que le {} dans lequel ils sont définis, dans tous les cas. Un point mineur, mais un point.

Enfin, pour faire ce que vous voulez, vous pouvez ajouter une série de crochets autour de la capture d’essai. Vous donne l’ampleur que vous souhaitez, même s’il en coûte un peu de lisibilité, mais pas trop.

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

Je pense que parce que quelque chose dans le bloc try a déclenché l'exception, le contenu de son espace de nom ne peut pas être approuvé - en d'autres termes, le fait de faire référence à la chaîne 'dans le bloc catch pourrait provoquer la levée d'une autre exception.

Eh bien, si cela ne génère pas d'erreur de compilation et que vous pouvez le déclarer pour le reste de la méthode, il n'y aurait aucun moyen de le déclarer uniquement dans la portée de try. Cela vous oblige à être explicite quant à savoir où la variable est supposée exister et ne fait pas de suppositions.

Si nous ignorons un instant le problème de la portée du champ d'application, le déclarant devra travailler beaucoup plus fort dans une situation mal définie. Bien que cela ne soit pas impossible, l’erreur de portée vous oblige également, l’auteur du code, à réaliser l’implication du code que vous écrivez (la chaîne s peut être nulle dans le bloc catch). Si votre code était légal, dans le cas d'une exception OutOfMemory, il n'est même pas garanti que s se voit attribuer un emplacement mémoire:

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

Le CLR (et donc le compilateur) vous oblige également à initialiser les variables avant leur utilisation. Dans le bloc catch présenté, cela ne peut pas le garantir.

Nous nous retrouvons donc avec le compilateur devant faire beaucoup de travail, ce qui en pratique n’apporte pas beaucoup d’avantages et serait probablement source de confusion pour les gens et les inciterait à se demander pourquoi essayer / attraper fonctionne différemment.

Outre la cohérence, le compilateur et le CLR sont en mesure de fournir une garantie plus grande de l’état d’une variable à l’intérieur d’un bloc capturé en ne permettant aucune fantaisie et en adhérant à la sémantique de périmètre déjà établie utilisée dans le langage. Qu'il existe et a été initialisé.

Notez que les concepteurs de langage ont fait du bon travail avec d'autres constructions telles que en utilisant et lock où le problème et l'étendue sont bien définis, ce qui vous permet d'écrire du code plus clair. .

par exemple. le mot-clé utilisant avec des objets IDisposable dans:

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

est équivalent à:

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

Si votre try / catch / finally est difficile à comprendre, essayez de refactoriser ou d'introduire une autre couche d'indirection avec une classe intermédiaire encapsulant la sémantique de ce que vous essayez d'accomplir. Sans voir le vrai code, il est difficile d'être plus précis.

Au lieu d'une variable locale, une propriété publique pourrait être déclarée; cela devrait également éviter une autre erreur potentielle d'une variable non affectée. chaîne publique S {get; ensemble; }

La Spécification C # (15.2) déclare " La portée d’une variable ou d’une constante locale déclarée dans un bloc correspond au bloc. "

(dans votre premier exemple, le bloc try est le bloc où "s" est déclaré)

Si l'opération d'affectation échoue, votre instruction catch aura une référence null à la variable non affectée.

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;
})();
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top