Почему переменные не объявлены в "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 ]

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

Две вещи:

  1. Как правило, Java имеет только 2 уровня: глобальный и функциональный. Но try / 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 не будут объявлены. Учитывая, что перехватчики предназначены для обработки исключительных обстоятельств и, наконец, должен выполнить, будучи безопасным, и объявить это проблемой во время компиляции намного лучше, чем во время выполнения.

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

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

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

Традиционно, в языках стиля 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.

9 голосов
/ 19 сентября 2008

Все остальные изучили основы - то, что происходит в блоке, остается в блоке. Но в случае .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.

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

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

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

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

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

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

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

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

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

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

        throw new Exception(":(")
    }
    catch (Exception e)
    {
        Debug.WriteLine(s);
    }
}
4 голосов
/ 18 сентября 2008

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

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

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

Есть несколько предложений, чтобы сделать код красивее. См 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
}

ОБНОВЛЕНИЕ: ARM реализован в Java 7. http://download.java.net/jdk7/docs/technotes/guides/language/try-with-resources.html

3 голосов
/ 19 сентября 2008

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

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

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

...