Почему переменные не объявлены в "try" в области видимости в "catch" или "finally"? - PullRequest
126 голосов
/ 18 сентября 2008

В C # и в Java (и, возможно, также в других языках) переменные, объявленные в блоке try, не входят в область действия соответствующих блоков «catch» или «finally». Например, следующий код не компилируется:

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

В этом коде ошибка времени компиляции возникает при ссылке на s в блоке catch, потому что s находится только в области видимости в блоке try. (В Java ошибка компиляции: «s не может быть решена»; в C # это «имя s не существует в текущем контексте».)

Похоже, что общим решением этой проблемы является объявление переменных непосредственно перед блоком try, а не внутри блока try:

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

Однако, по крайней мере для меня, (1) это выглядит как неуклюжее решение, и (2) это приводит к тому, что переменные имеют больший объем, чем предполагал программист (весь оставшийся метод, а не только в контекст try-catch-finally).

Мой вопрос заключается в том, каковы были (-ы) обоснования (-и) за этим решением по проектированию языка (в Java, в C # и / или в любых других применимых языках)?

Ответы [ 28 ]

2 голосов
/ 18 сентября 2008

Потому что блок try и блок catch - это 2 разных блока.

В следующем коде вы ожидаете, что s, определенные в блоке A, будут видны в блоке B?

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

{ // block B
  Console.Out.WriteLine(s); // or printf or whatever
}
2 голосов
/ 18 сентября 2008

Переменные имеют уровень блока и ограничены этим блоком Try или Catch. Аналогично определению переменной в операторе if. Подумайте об этой ситуации.

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

Строка никогда не будет объявлена, поэтому на нее нельзя полагаться.

2 голосов
/ 18 сентября 2008

Вы решение именно то, что вы должны сделать. Вы не можете быть уверены, что ваше объявление было даже достигнуто в блоке try, что приведет к другому исключению в блоке catch.

Он просто должен работать как отдельные области.

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
1 голос
/ 18 сентября 2008

Одна из причин, по которой они не находятся в одной и той же области видимости, заключается в том, что в любой точке блока try вы можете выдать исключение. Если бы они находились в одной и той же области, ожидание было катастрофой, потому что в зависимости от того, где было сгенерировано исключение, это могло бы быть даже более двусмысленным.

По крайней мере, когда она объявлена ​​вне блока try, вы точно знаете, какой может быть минимальная переменная при возникновении исключения; Значение переменной перед блоком try.

1 голос
/ 18 сентября 2008

В приведенном вами конкретном примере инициализация s не может вызвать исключение. Таким образом, вы могли бы подумать, что, возможно, его область может быть расширена.

Но в общем случае выражения инициализатора могут выдавать исключения. Не имеет смысла, чтобы переменная, инициализатор которой выдал исключение (или которая была объявлена ​​после другой переменной, где это произошло), находилась в области видимости для catch / finally.

Кроме того, ухудшается читаемость кода. Правило в C (и следующих за ним языках, включая C ++, Java и C #) простое: переменные области следуют за блоками.

Если вы хотите, чтобы переменная находилась в области видимости для try / catch / finally, но нигде больше, то оберните все это в другой набор скобок (пустой блок) и объявите переменную перед попыткой.

1 голос
/ 18 сентября 2008

Когда вы объявляете локальную переменную, она помещается в стек (для некоторых типов все значение объекта будет в стеке, для других типов в стеке будет только ссылка). Когда в блоке try возникает исключение, локальные переменные в блоке освобождаются, что означает, что стек «разматывается» обратно в состояние, в котором он находился в начале блока try. Это по замыслу. Это то, как try / catch может отменить все вызовы функций в блоке и вернуть вашу систему в рабочее состояние. Без этого механизма вы никогда не сможете быть уверены в состоянии чего-либо, когда возникает исключение.

То, что ваш код обработки ошибок полагается на объявленные извне переменные, значения которых изменены внутри блока try, кажется мне плохим дизайном. То, что вы делаете, - это, по сути, утечка ресурсов преднамеренно для получения информации (в данном конкретном случае это не так плохо, потому что вы только утечку информации, но представьте, что это какой-то другой ресурс? будущее). Я бы предложил разбить ваши блоки try на более мелкие куски, если вам требуется больше детализации при обработке ошибок.

1 голос
/ 18 сентября 2008

Хотя в вашем примере странно, что он не работает, возьмите вот такой:

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

Это приведет к тому, что перехватчик сгенерирует исключение нулевой ссылки, если код 1 прервался. Теперь, хотя семантика try / catch довольно хорошо понятна, это был бы досадный угловой случай, поскольку s определяется с начальным значением, поэтому теоретически он никогда не должен быть нулевым, но при разделяемой семантике это будет *

Опять же, теоретически это можно исправить, разрешив только отдельные определения (String s; s = "1|2";) или какой-то другой набор условий, но обычно проще просто сказать нет.

Кроме того, он позволяет определять семантику области видимости глобально без исключения, в частности, локальные элементы действуют до тех пор, пока {} они определены во всех случаях. Незначительная точка, но точка.

Наконец, чтобы сделать то, что вы хотите, вы можете добавить набор скобок вокруг try catch. Дает вам объем, который вы хотите, хотя это достигается за счет небольшой читабельности, но не слишком много.

{
     String s;
     try
     {
          s = "test";
          //More code
     }
     catch
     {
          Console.WriteLine(s);
     }
}
1 голос
/ 29 декабря 2010

Что делать, если исключение выдается в некотором коде, который находится выше объявления переменной. Это означает, что в этом случае сама декларация не произошла.

try {

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

} catch (Exception) {
        //Use s;//Problem here
} finally {
        //Use s;//Problem here
}
1 голос
/ 19 сентября 2008

В Python они видны в блоках catch / finally, если строка, объявляющая их, не выдает.

1 голос
/ 18 сентября 2008

Как отмечали другие пользователи, фигурные скобки определяют область видимости практически во всех языках стиля C, которые мне известны.

Если это простая переменная, то почему вас волнует, как долго она будет находиться в области видимости? Это не так уж важно.

в C #, если это сложная переменная, вы захотите реализовать IDisposable. Затем вы можете использовать try / catch / finally и вызвать obj.Dispose () в блоке finally. Или вы можете использовать ключевое слово using, которое автоматически вызовет Dispose в конце раздела кода.

...