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

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

Вопрос

В 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 # и / или на любых других применимых языках)?

Это было полезно?

Решение

Две вещи:

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

  2. (и что еще более важно).Вы не можете знать, где в блоке 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 не будут объявлены.Учитывая, что уловы предназначены для обработки исключительных обстоятельств и, наконец, должен выполнять, будучи в безопасности и объявляя это проблемой во время компиляции, намного лучше, чем во время выполнения.

Другие советы

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

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

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

Редактировать:Я думаю, что каждое правило делает сделайте исключение.Следующее является допустимым для C ++:

int f() { return 0; }

void main() 
{
    int y = 0;

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

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

Все остальные усвоили основы - то, что происходит в блоке, остается в блоке.Но в случае .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 уважает блоки - они неотъемлемо являются частью базового кода, генерируемого при компиляции вашего C #.Область действия жестко задана не только в спецификации C #, но и в спецификациях CLR и CLS.

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

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

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

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

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

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

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

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

У @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.

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

Есть несколько предложений по улучшению кода.Видишь РУКА

 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
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

try {

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

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

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

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

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

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

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

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

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

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

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

Если мы на мгновение проигнорируем проблему с блоком определения области видимости, компилятору пришлось бы работать намного усерднее в ситуации, которая четко не определена.Хотя это не является невозможным, ошибка определения области видимости также вынуждает вас, автора кода, осознать значение кода, который вы пишете (что строка 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.Что он существует и был инициализирован.

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

например ,тот самый используя ключевое слово с 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 / finally труден для понимания, попробуйте провести рефакторинг или ввести другой уровень косвенности с промежуточным классом, который инкапсулирует семантику того, чего вы пытаетесь достичь.Не видя реального кода, трудно быть более конкретным.

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

Тот Самый Спецификация C # (15.2) утверждает, что "Область действия локальной переменной или константы, объявленной в блоке, является самим блоком".

(в вашем первом примере блок try - это блок, в котором объявлен "s")

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

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;
})();
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top