Почему переменные не объявлены в "try" в области "catch" или "finally"?

В С# и в 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 не может быть разрешена"; в С# она "Имя" не существует в текущем контексте ".)

Общее решение этой проблемы состоит в том, чтобы вместо этого объявлять переменные непосредственно перед блоком 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, на С# и/или на любых других применимых языках)?

Ответ 1

Две вещи:

  • Как правило, Java имеет всего 2 уровня охвата: глобальный и функциональный. Но try/catch - исключение (каламбур не предназначен). Когда генерируется исключение, и объект исключения получает назначенную ему переменную, эта переменная объекта доступна только в разделе "catch" и уничтожается сразу после завершения catch.

  • (и что более важно). Вы не можете знать, где в блоке try было выбрано исключение. Возможно, это было до объявления переменной. Поэтому невозможно сказать, какие переменные будут доступны для предложения catch/finally. Рассмотрим следующий случай, когда область видимости такова, как вы предполагали:

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

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

Ответ 2

Как вы могли быть уверены, что достигли части декларации в своем блоке catch? Что делать, если экземпляр вызывает исключение?

Ответ 3

Традиционно, в языках C-стиля, то, что происходит внутри фигурных фигурных скобок, остается внутри фигурных скобок. Я думаю, что наличие времени переменного растяжения в таких областях было бы неинтуитивным для большинства программистов. Вы можете добиться того, чего хотите, включив блоки try/catch/finally внутри другого уровня фигурных скобок. например.

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

EDIT: Я думаю, у каждого правила есть исключение. Допустим, что С++:

int f() { return 0; }

void main() 
{
    int y = 0;

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

Область действия x является условным, предложением then и предложением else.

Ответ 4

В С++, во всяком случае, область автоматической переменной ограничена фигурными фигурными скобками, которые ее окружают. Почему кто-то ожидает, что это будет отличаться, перелистывая ключевое слово try за фигурные скобки?

Ответ 5

Все остальные подняли основы - что происходит в блоке, остается в блоке. Но в случае .NET может оказаться полезным изучить, что думает компилятор. Возьмем, к примеру, следующий код try/catch (обратите внимание, что StreamReader объявлен правильно за пределами блоков):

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

В 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

Что мы видим? MSIL уважает блоки - они являются неотъемлемой частью базового кода, сгенерированного при компиляции вашего С#. Объем не просто установлен в спецификации С#, но и в CLR и CLS.

Область защиты защищает вас, но иногда вам приходится ее обходить. Со временем вы привыкаете к этому, и он начинает чувствовать себя естественным. Как и все остальные, что происходит в блоке, остается в этом блоке. Вы хотите поделиться чем-то? Вы должны выйти за пределы блоков...

Ответ 6

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

Исключением, кстати, является JavaScript, который имеет похожий синтаксис, но является областью действия. В JavaScript переменная, объявленная в блоке try, находится в области видимости в блоке catch и везде в ее содержащей функции.

Ответ 7

Как указано в ravenspoint, все ожидают, что переменные будут локальными для блока, в котором они определены. try вводит блок, а значит, catch.

Если вы хотите, чтобы переменные были локальными как для try, так и catch, попробуйте включить оба блока:

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

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

Ответ 8

@burkhard задает вопрос, почему правильно ответил, но в качестве примечания я хотел добавить, в то время как ваш рекомендуемый пример решения хорош 99.9999 +% времени, это не очень хорошая практика, гораздо безопаснее либо проверить null, прежде чем использовать что-либо, созданное в блоке try, или инициализировать переменную чем-то вместо того, чтобы просто объявлять ее перед блоком try. Например:

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

Или:

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

Это должно обеспечить масштабируемость в обходном пути, так что даже если то, что вы делаете в блоке try, сложнее, чем назначать строку, вы должны иметь возможность безопасно получать доступ к данным из вашего блока catch.

Ответ 9

Ответ, как отмечали все, в значительной степени "определяет, как определяются блоки".

Есть несколько предложений, чтобы сделать код более красивым. См. ARM

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

Закрытия должны также решить эту проблему.

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

UPDATE: ARM реализована на Java 7. http://download.java.net/jdk7/docs/technotes/guides/language/try-with-resources.html

Ответ 10

В соответствии с разделом "Как выбрасывать и выхватывать исключения" в уроке 2 из набора для самостоятельного обучения MCTS (экзамен 70-536): Microsoft®.NET Framework 2.0-Application Development Foundation, причина в том, что исключение могло произойти до объявления переменных в блоке try (как уже отмечали другие).

Цитата со страницы 25:

"Обратите внимание, что объявление StreamReader было перемещено за пределы блока Try в предыдущем примере. Это необходимо, потому что блок finally не может обращаться к переменным, объявленным в блоке Try. Это имеет смысл, потому что в зависимости от того, где исключение, объявления переменных в блоке Try могут еще не выполняться.

Ответ 11

Ваше решение - именно то, что вы должны делать. Вы не можете быть уверены, что ваше объявление было даже достигнуто в блоке 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

Ответ 12

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

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

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

Ответ 13

Поскольку блок try и блок catch являются двумя разными блоками.

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

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

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

Ответ 14

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

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

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

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

Ответ 15

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

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

Ответ 16

While in your example it is weird that it does not work, take this similar one:

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

This would cause the catch to throw a null reference exception if Code 1 broke. Now while the semantics of try/catch are pretty well understood, this would be an annoying corner case, since s is defined with an initial value, so it should in theory never be null, but under shared semantics, it would be.

Again this could in theory be fixed by only allowing separated definitions (String s; s = "1|2";), or some other set of conditions, but it is generally easier to just say no.

Additionally, it allows the semantics of scope to be defined globally without exception, specifically, locals last as long as the {} they are defined in, in all cases. Minor point, but a point.

Finally, in order to do what you want, you can add a set of brackets around the try catch. Gives you the scope you want, although it does come at the cost of a little readability, but not too much.

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

Ответ 17

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

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

Ответ 18

Когда у вас есть попытка поймать, вы должны в большинстве своем знать, что ошибки, которые он может бросить. Эти классы Exception normaly рассказывают все, что вам нужно об исключении. Если нет, вы должны сделать свои собственные классы исключений и передать эту информацию. Таким образом, вам не нужно будет получать переменные из блока try, потому что Exception является самообучающим. Поэтому, если вам нужно сделать это много, подумайте о своем дизайне и попытайтесь подумать, есть ли какой-то другой способ, вы можете либо предсказать исключения, либо использовать информацию, исходящую из исключений, а затем, возможно, изменить ваши собственные исключение с дополнительной информацией.

Ответ 19

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

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

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

Ответ 20

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

Ответ 21

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

try {

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

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

Ответ 22

Я думал, что, поскольку что-то в блоке try вызвало исключение, его содержимое пространства имен не может быть доверено, то есть ссылка на String 's' в блоке catch может вызвать бросок еще одного исключения.

Ответ 23

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

Ответ 24

Если операция присваивания завершилась неудачей, ваш оператор catch будет иметь нулевую ссылку на непризнанную переменную.

Ответ 25

С# Spec (15.2) указывает: "Область локальной переменной или константы, объявленной в блоке, является блоком".

(в первом примере блок try является блоком, где объявлено "s" )

С уважением, Tamberg

Ответ 26

Если на какой-то момент игнорировать проблему области видимости, то в ситуации, которая недостаточно четко определена, сложнее будет работать намного сложнее. Хотя это не невозможно, ошибка определения области охвата также заставляет вас, автора кода, реализовать импликацию кода, который вы пишете (что строка s может быть нулевой в блоке catch). Если ваш код был законным, в случае исключения OutOfMemory s даже не гарантированно выделяется слот памяти:

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

CLR (и, следовательно, компилятор) также заставляет вас инициализировать переменные до их использования. В представленном блоке catch он не может этого гарантировать.

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

В дополнение к согласованности, не позволяя чему-либо притворяться и придерживаться уже установленной семантики семантики, используемой на всем языке, компилятор и CLR могут обеспечить большую гарантию состояния переменной внутри блока catch. Что он существует и был инициализирован.

Обратите внимание, что разработчики языка отлично поработали с другими конструкциями, такими как использование и блокировка, где хорошо определены проблема и область, что позволяет писать более четкий код.

например. ключевое слово using с IDisposable объектами в:

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

эквивалентно:

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

Если ваш try/catch/окончательно трудно понять, попробуйте рефакторинг или введите другой слой косвенности с промежуточным классом, который инкапсулирует семантику того, что вы пытаетесь выполнить. Не видя реального кода, трудно быть более конкретным.

Ответ 27

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

Ответ 28

Вместо локальной переменной может быть объявлено публичное свойство; это также должно избегать другой потенциальной ошибки неназначенной переменной. public string S {get; задавать; }