"catch"또는 "finally"범위에서 "try"로 변수가 선언되지 않은 이유는 무엇입니까?


139

C # 및 Java (및 다른 언어도 가능)에서 "try"블록에 선언 된 변수는 해당 "catch"또는 "finally"블록의 범위에 속하지 않습니다. 예를 들어 다음 코드는 컴파일되지 않습니다.

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

이 코드에서는 catch 블록의 s에 대한 참조에서 컴파일 타임 오류가 발생합니다. 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) 변수가 프로그래머가 의도 한 것보다 더 넓은 범위를 갖습니다 (메소드의 맥락 에서만가 아니라 메소드의 전체 나머지). 마지막으로 시도하십시오.

내 질문은이 언어 디자인 결정의 근거가 무엇입니까 (Java, C # 및 / 또는 기타 적용 가능한 언어)?

답변:


171

두가지:

  1. 일반적으로 Java는 전역 및 기능의 두 가지 범위 만 있습니다. 그러나 try / 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

캐치 블록의 선언 부분에 도달했다는 것을 어떻게 확신 할 수 있습니까? 인스턴스화에서 예외가 발생하면 어떻게됩니까?


6
응? 변수 선언은 예외를 발생시키지 않습니다.
Joshua

6
동의, 예외를 던질 수있는 인스턴스화입니다.
Burkhard

19

전통적으로 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 절입니다.


10

다른 모든 사람들은 기본 사항을 가져 왔습니다. 블록에서 발생하는 일이 블록에 남아 있습니다. 그러나 .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

C ++에서 자동 변수의 범위는이를 둘러싸는 중괄호로 제한됩니다. 왜 중괄호 밖에서 try 키워드를 내려서 이것이 다른 것이기를 기대할까요?


1
합의 "}"는 범위 끝을 의미합니다. 그러나 try-catch-finally는 try 블록 이후 에 catch 및 / 또는 finally 블록 이 있어야 한다는 점에서 드문 경우입니다 . 따라서 try / cat의 범위가 관련된 catch / finally로 전달되는 일반적인 규칙에 대한 예외가 허용되는 것 같습니까?
Jon Schneider

7

ravenspoint가 지적한 것처럼, 모든 사람들은 변수가 정의 된 블록에 국한 될 것으로 기대합니다. try블록을 소개합니다 .catch .

try및 에 모두 지역 변수가 필요한 경우 catch두 블록을 모두 블록으로 묶어보십시오.

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

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

5

간단한 대답은 C와 구문을 상속받은 대부분의 언어는 블록 범위입니다. 즉, 변수가 하나의 블록, 즉 {} 내부에 정의되어 있으면 해당 범위가됩니다.

그런데 예외는 비슷한 구문을 가지고 있지만 기능 범위가있는 JavaScript입니다. JavaScript에서 try 블록에 선언 된 변수는 catch 블록과 그 밖의 다른 곳에서 포함 된 함수의 범위에 있습니다.


4

@ 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

MCTS 자체 학습 교육 키트 (시험 70-536) 의 단원 2에서 "예외를 던지고 잡는 방법"섹션에 따르면 : Microsoft® .NET Framework 2.0—Application Development Foundation 의 예외가 발생했을 수 있습니다. try 블록에서 변수 선언 앞에 (다른 사람들이 이미 언급했듯이).

25 쪽 인용 :

"이전 예제에서는 StreamReader 선언이 Try 블록 외부로 이동했음을 알 수 있습니다. 마지막 블록은 Try 블록 내에 선언 된 변수에 액세스 할 수 없기 때문에 필요합니다. 예외가 발생한 위치에 따라 변수 선언이 시도 차단이 아직 실행되지 않았을 수 있습니다 . "


4

모든 사람들이 지적했듯이 대답은 "블록이 어떻게 정의되는지"입니다.

코드를 더 예쁘게 만들기위한 제안이 있습니다. 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


2

당신이 해결해야 할 일은 정확히 당신입니다. 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

2

변수는 블록 레벨이며 해당 Try 또는 Catch 블록으로 제한됩니다. if 문에서 변수를 정의하는 것과 비슷합니다. 이 상황을 생각해보십시오.

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

문자열은 선언되지 않으므로 의존 할 수 없습니다.


2

try 블록과 catch 블록은 서로 다른 두 블록이기 때문입니다.

다음 코드에서 블록 A에 정의 된 s가 블록 B에 표시 될 것으로 예상합니까?

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

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

2

귀하의 예에서 작동하지 않는 것이 이상하지만이 비슷한 것을 취하십시오.

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

이로 인해 코드 1이 중단되면 catch에서 널 참조 예외가 발생합니다. try / catch의 의미는 꽤 잘 이해되지만 s는 초기 값으로 정의되므로 이론적으로 null이 아니어야하지만 공유 의미론에서는 그렇지 않습니다.

다시 말해서 이것은 이론적으로 분리 된 정의 ( String s; s = "1|2";) 또는 다른 조건 세트 만 허용하여 해결할 수 있지만 일반적으로 아니오라고 말하는 것이 더 쉽습니다.

또한 범위의 의미를 예외없이 전역 적으로 정의 할 수 있습니다. 특히 로컬 {}은 모든 경우에 정의 된 기간 동안 지속됩니다 . 사소한 점이지만 한 점입니다.

마지막으로 원하는 것을하기 위해 try catch 주위에 대괄호 세트를 추가 할 수 있습니다. 약간의 가독성이 있지만 너무 많은 것은 아니지만 원하는 범위를 제공합니다.

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

1

주어진 특정 예에서 초기화는 예외를 던질 수 없습니다. 따라서 범위가 확장 될 수 있다고 생각합니다.

그러나 일반적으로 이니셜 라이저 표현식은 예외를 발생시킬 수 있습니다. 이니셜 라이저가 예외를 던진 변수 (또는 그 변수가 발생한 다른 변수 다음에 선언 된)는 catch / final 범위에 속하는 변수에 대해서는 의미가 없습니다.

또한 코드 가독성이 떨어집니다. C의 규칙 (및 C ++, Java 및 C #을 포함하여 그에 따르는 언어)은 간단합니다. 변수 범위는 블록을 따릅니다.

변수가 try / catch / finally 범위를 벗어나지 않으려면 다른 모든 괄호 세트 (베어 블록)로 모든 것을 감싸고 try 전에 변수를 선언하십시오.


1

그들이 같은 범위에 있지 않은 이유 중 일부는 try 블록의 어느 시점에서나 예외를 던질 수 있기 때문입니다. 그들이 같은 범위에 있다면, 예외가 발생한 곳에 따라 더 모호 할 수 있기 때문에 기다리는 재앙입니다.

최소한 try 블록 외부에서 선언되면 예외가 발생했을 때 변수가 무엇인지 최소한 알 수 있습니다. try 블록 앞의 변수 값입니다.


1

로컬 변수를 선언하면 스택에 배치됩니다 (일부 유형의 경우 객체의 전체 값이 스택에 있고 다른 유형의 경우 참조 만 스택에 있음). try 블록 내에 예외가 있으면 블록 내의 로컬 변수가 해제됩니다. 즉, 스택이 try 블록의 시작 부분에 있던 상태로 다시 "풀립니다". 이것은 의도적으로 설계된 동작입니다. try / catch가 블록 내의 모든 함수 호출을 취소하고 시스템을 다시 기능 상태로 만드는 방법입니다. 이 메커니즘이 없으면 예외가 발생했을 때 어떤 상태도 확인할 수 없습니다.

오류 처리 코드가 try 블록 내에서 값이 변경된 외부 선언 변수에 의존하게하는 것은 나에게 나쁜 디자인처럼 보입니다. 당신이하고있는 일은 정보를 얻기 위해 의도적으로 자원을 유출하는 것입니다 (이 경우에는 정보가 유출되기 때문에 그렇게 나쁘지는 않지만 다른 자원인지 상상해보십시오. 미래). 오류 처리에 더 세분성이 필요한 경우 try 블록을 더 작은 청크로 나누는 것이 좋습니다.


1

try catch를 사용하면 대부분 오류가 발생할 수 있음을 알아야합니다. Theese 예외 클래스는 정상적으로 예외에 대해 필요한 모든 것을 알려줍니다. 그렇지 않은 경우, 자신 만의 예외 클래스를 만들고 그 정보를 전달해야합니다. 그렇게하면 예외는 자체 설명이기 때문에 try 블록 내부에서 변수를 가져올 필요가 없습니다. 따라서이 작업을 많이 수행해야하는 경우 디자인에 대해 생각하고 다른 방법이 있는지 생각해보십시오. 예외가 발생할 경우를 예측하거나 예외에서 오는 정보를 사용한 다음 자신의 문제를 다시 던질 수 있습니다. 자세한 내용은 예외입니다.


1

다른 사용자가 지적했듯이 중괄호는 내가 아는 거의 모든 C 스타일 언어로 범위를 정의합니다.

변수가 단순한 경우 범위가 얼마나 오래 지속 되는가? 그렇게 큰 문제는 아닙니다.

C #에서 변수가 복잡한 경우 IDisposable을 구현하려고합니다. 그런 다음 try / catch / finally를 사용하고 finally 블록에서 obj.Dispose ()를 호출 할 수 있습니다. 또는 using 키워드를 사용하면 코드 섹션의 끝에 Dispose가 자동으로 호출됩니다.


1

파이썬에서는 선언 라인이 던지지 않으면 catch / finally 블록에서 볼 수 있습니다.


1

변수 선언보다 위에있는 일부 코드에서 예외가 발생하면 어떻게됩니까? 이 경우 선언 자체가 발생하지 않았 음을 의미합니다.

try {

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

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

1

C # 사양 (15.2)는 "로컬 변수 또는 상수의 범위는 블록 IST 블록 선언."상태

첫 번째 예에서 try 블록은 "s"가 선언 된 블록입니다.


0

내 생각은 try 블록의 무언가가 예외를 트리거했기 때문에 네임 스페이스 내용을 신뢰할 수 없습니다. 즉 catch 블록에서 String 's를 참조하면 또 다른 예외가 발생할 수 있습니다.


0

컴파일 오류가 발생하지 않고 나머지 메소드에 대해 선언 할 수 있다면 try 범위 내에서만 선언 할 수있는 방법이 없습니다. 변수가 존재하고 가정하지 않는 위치에 대해 명시 적으로 강요합니다.


0

범위 제한 문제를 잠시 무시하면 잘 정의되지 않은 상황에서 컴파일러가 훨씬 더 열심히 노력해야합니다. 이것이 불가능하지는 않지만, 범위 지정 오류는 또한 코드 작성자 인 사용자가 작성한 코드의 의미를 인식하도록합니다 (캐치 블록에서 문자열 s가 null 일 수 있음). 코드가 합법적이라면 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 키워드 :

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 이해하기 어려운 경우 달성하려는 의미를 캡슐화하는 중간 클래스를 사용하여 리팩토링 또는 다른 간접 계층을 도입하십시오. 실제 코드를 보지 않으면 더 구체적으로 표현하기가 어렵습니다.


0

지역 변수 대신 공공 재산을 선언 할 수 있습니다. 또한 할당되지 않은 변수의 또 다른 잠재적 오류를 피해야합니다. 공개 문자열 S {get; 세트; }


-1

할당 작업이 실패하면 catch 문은 할당되지 않은 변수에 대한 null 참조를 갖습니다.


2
할당되지 않았습니다. 인스턴스 및 정적 변수와 달리 null도 아닙니다.
Tom Hawtin-tackline

-1

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

WTF? 다운 투표를해야하는 이유 캡슐화는 OOP에 필수적입니다. 너무 예뻐요.
핵심

2
나는 downvote가 아니었지만 잘못된 것은 초기화되지 않은 문자열을 반환하는 것입니다.
Ben Voigt
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.