연결할 수없는 코드이지만 예외로 연결할 수 있습니다.


108

이 코드는 ODBC 연결 데이터베이스에서 읽고 쓰는 응용 프로그램의 일부입니다. 데이터베이스에 레코드를 만든 다음 레코드가 성공적으로 생성되었는지 확인한 다음 true.

제어 흐름에 대한 나의 이해는 다음과 같습니다.

command.ExecuteNonQuery()Invalid​Operation​Exception"객체의 현재 상태에 대해 메서드 호출이 유효하지 않음" 을 throw하도록 문서화되어 있습니다. 따라서 이런 일이 발생하면 try블록 실행 이 중지되고 finally블록이 실행 된 다음 return false;맨 아래에서 실행 됩니다.

그러나 내 IDE return false;는에 도달 할 수없는 코드 라고 주장합니다 . 그리고 그것은 사실 인 것 같습니다. 나는 그것을 제거 할 수 있으며 불만없이 컴파일됩니다. 그러나 저에게는 언급 된 예외가 발생하는 코드 경로에 대한 반환 값이없는 것처럼 보입니다.

private static bool createRecord(String table,
                                 IDictionary<String,String> data,
                                 System.Data.IDbConnection conn,
                                 OdbcTransaction trans) {

    [... some other code ...]

    int returnValue = 0;
    try {
        command.CommandText = sb.ToString();
        returnValue = command.ExecuteNonQuery();

        return returnValue == 1;
    } finally {
        command.Dispose();
    }

    return false;
}

여기서 이해의 오류는 무엇입니까?



41
사이드 노트 : 호출하지 않습니다 Dispose명시 적으로,하지만 넣어 using:using (var command = ...) {command.CommandText = sb.ToString(); return command.ExecuteNonQuery(); }
드미트리 Bychenko

7
finally블록은 당신이 생각하는 것보다 다른 것을 의미한다.
Thorbjørn Ravn Andersen

답변:


149

컴파일러 경고 (수준 2) CS0162

연결할 수없는 코드가 감지되었습니다.

컴파일러가 실행되지 않을 코드를 감지했습니다.

즉, 컴파일러는 정적 분석 을 통해 도달 할 수 없다는 것을 충분히 이해 하고 컴파일 된 IL 에서 완전히 생략합니다 (따라서 경고).

참고 : 디버거를 사용하여 연결할 수없는 코드로 이동하거나 IL 탐색기를 사용하여이 사실을 스스로 증명할 수 있습니다.

finally온 실행할 수 있습니다 예외 가 여전히있을 것입니다 (옆으로하지만)가 (이 경우) 사실을 변경하지 않습니다, 캐치되지 않는 예외 . Ergo, 마지막 return은 결코 맞지 않습니다.

  • 코드를 마지막으로 계속하려면 return유일한 옵션은 예외잡아내는 것입니다 .

  • 그렇지 않은 경우 그대로두고 return.

try 
{
    command.CommandText = sb.ToString();
    returnValue = command.ExecuteNonQuery();

    return returnValue == 1;
}
catch(<some exception>)
{
   // do something
}
finally 
{
    command.Dispose();
}

return false;

문서를 인용하려면

try-finally (C # 참조)

finally 블록을 사용하면 try 블록에 할당 된 모든 리소스를 정리할 수 있으며 try 블록에서 예외가 발생하더라도 코드를 실행할 수 있습니다. 일반적으로 제어가 try 문을 떠날 때 finally 블록의 문이 실행됩니다. 제어 전송은 정상 실행, break, continue, goto 또는 return 문 실행 또는 try 문 외부로 예외 전파의 결과로 발생할 수 있습니다.

처리 된 예외 내에서 연관된 finally 블록이 실행되도록 보장됩니다. 그러나 예외가 처리되지 않은 경우 finally 블록의 실행은 예외 해제 작업이 트리거되는 방법에 따라 다릅니다. 이는 컴퓨터가 어떻게 설정되었는지에 따라 달라집니다.

일반적으로 처리되지 않은 예외가 응용 프로그램을 종료 할 때 finally 블록이 실행되는지 여부는 중요하지 않습니다. 그러나 해당 상황에서도 실행해야하는 finally 블록에 명령문이있는 경우 한 가지 해결책은 try-finally 명령문에 catch 블록을 추가하는 것 입니다. 또는 call stack 위의 try-finally 문의 try 블록에서 발생할 수있는 예외를 포착 할 수 있습니다 . 즉, try-finally 문이 포함 된 메서드를 호출하는 메서드, 해당 메서드를 호출하는 메서드 또는 호출 스택의 모든 메서드에서 예외를 포착 할 수 있습니다. 예외가 포착되지 않으면 finally 블록의 실행은 운영 체제가 예외 해제 작업을 트리거하도록 선택했는지 여부에 따라 달라집니다.

마지막으로

IDisposable인터페이스 를 지원하는 모든 것을 사용할 때 (관리되지 않는 리소스를 해제하도록 설계됨) using문으로 래핑 할 수 있습니다 . 컴파일러는 객체를 생성 try {} finally {}하고 내부적으로 호출 Dispose()합니다.


1
첫 번째 문장에서 IL은 무엇을 의미합니까?
Clockwork

2
@Clockwork IL 은 고급 .NET 언어로 작성된 코드를 컴파일 한 제품입니다. 이러한 언어 중 하나로 작성된 코드를 컴파일하면 IL로 만들어진 바이너리가 생성됩니다. . 중간 언어는 때로는 공통 중간 언어 (CIL) 또는 Microsoft 중간 언어 (MSIL)라고합니다,
마이클 랜달

1
간단히 말해서, 그가 가능성을 포착하지 못했기 때문에 다음과 같습니다. 던졌다.
Felype

86

finally 블록이 실행 된 다음 false 반환을 실행합니다. 하단에.

잘못된. finally예외를 삼키지 않습니다. 이를 존중하고 예외는 정상적으로 처리됩니다. 블록이 끝나기 전에 (예외 유무에 관계없이) finally에서 코드를 실행합니다.

예외를 삼키려면 catchno throw가 있는 블록을 사용해야 합니다.


1
위의 sinppet은 예외 상황에서 컴파일됩니까?
Ehsan Sajjad

3
그것은 컴파일 않지만, 공격하지 않습니다 return false예외를 던질 때문에 대신 @EhsanSajjad
패트릭 호프만을

1
이상하게 보이며, 예외가없는 경우 bool에 대한 값을 반환하고 예외의 경우 아무것도 반환하지 않기 때문에 컴파일하므로 메서드의 반환 유형을 충족하는 데 합법적입니까?
Ehsan Sajjad

2
컴파일러는 경고의 대상인 줄을 무시합니다. 그렇다면 그게 왜 이상할까요? @EhsanSajjad
패트릭 호프만

3
재미있는 사실 : 그것은 실제로 하지 예외가 프로그램에 적발되지 않은 경우 finally 블록이 실행됩니다 보장. 사양이 보장되지 않고 초기 CLRS는 않았다 NOT finally 블록을 실행합니다. 4.0 (이전 일 수 있음)부터는 동작이 변경되었다고 생각하지만 다른 런타임은 여전히 ​​다르게 동작 할 수 있습니다. 다소 놀라운 행동을합니다.
Voo

27

경고는 사용하지 않았고 catch메서드가 기본적으로 다음과 같이 작성 되었기 때문입니다 .

bool SomeMethod()
{
    return true;
    return false; // CS0162 Unreachable code detected
}

finally폐기 용 으로 만 사용하므로 using패턴 을 활용하는 것이 좋습니다 .

using(var command = new WhateverCommand())
{
     ...
}

그것이 무엇을 부르는지 확인하기에 충분 Dispose합니다. 코드 블록을 성공적으로 실행 한 후 또는 호출 스택에서 일부 catch 다운 (부모 호출이 다운 되었습니까?) 시 (전) 호출되도록 보장됩니다 .

폐기에 관한 것이 아니라면

try { ...; return true; } // only one return
finally { ... }

이 때문에, 충분히 결코 반환 할 필요가 없습니다 false방법의 끝에서 (해당 행에 대한 필요가 없습니다). 메서드는 명령 실행의 결과 ( true또는 false) 를 반환 하거나 그렇지 않으면 예외를 throw합니다 .


예상 예외를 래핑하여 자체 예외를 throw하는 것도 고려하십시오 ( InvalidOperationException 생성자 확인 ).

try { ... }
catch(SomeExpectedException e)
{
    throw new SomeBetterExceptionWithExplanaition("...", e);
}

이것은 일반적으로 중첩 된 호출 예외가 말하는 것보다 더 의미있는 (유용한) 것을 호출자에게 말하는 데 사용됩니다.


대부분의 경우 처리되지 않은 예외에 대해서는 신경 쓰지 않습니다. 때로는 finally예외가 처리되지 않은 경우에도 호출 되도록해야합니다 . 이 경우 단순히 직접 잡아서 다시 던집니다 ( 이 답변 참조 ).

try { ... }
catch { ...; throw; } // re-throw
finally { ... }

14

다음과 같은 것을 찾고있는 것 같습니다.

private static bool createRecord(string table,
                                 IDictionary<String,String> data,
                                 System.Data.IDbConnection conn,
                                 OdbcTransaction trans) {
  [... some other code ...]

  // Using: do not call Dispose() explicitly, but wrap IDisposable into using
  using (var command = ...) {
    try {
      // Normal flow:
      command.CommandText = sb.ToString();

      // True if and only if exactly one record affected
      return command.ExecuteNonQuery() == 1;
    }
    catch (DbException) {
      // Exceptional flow (all database exceptions)
      return false;
    }
  }
}

, 음, 그건 제발 finally 삼키지 않는 예외를

finally {
  // This code will be executed; the exception will be efficently re-thrown
}

// And this code will never be reached

8

당신은없는 catch예외가 여전히 블록 수익을 발생합니다 있도록 블록을.

finally 블록이 실행 된 다음 false 반환을 실행합니다. 하단에.

finally 블록이 실행되고 포착되지 않은 예외가 발생하기 때문에 이것은 잘못된 것입니다.

finally블록은 정리에 사용되며 예외를 포착하지 않습니다. 예외는 반환 전에 throw되므로 예외가 전에 throw되기 때문에 반환에 도달하지 않습니다.

예외가 발생하기 때문에 IDE에 도달하지 않는 것이 정확합니다. catch블록 만 예외를 포착 할 수 있습니다.

문서 에서 읽기 ,

일반적으로 처리되지 않은 예외가 응용 프로그램을 종료 할 때 finally 블록이 실행되는지 여부는 중요하지 않습니다. 그러나 해당 상황에서도 실행해야하는 finally 블록에 명령문이있는 경우 한 가지 해결책은 try-finally 명령문에 catch 블록을 추가하는 것 입니다. 또는 호출 스택 상위에있는 try-finally 문의 try 블록에서 발생할 수있는 예외를 포착 할 수 있습니다. 즉, try-finally 문이 포함 된 메서드를 호출하는 메서드, 해당 메서드를 호출하는 메서드 또는 호출 스택의 모든 메서드에서 예외를 포착 할 수 있습니다. 예외가 포착되지 않으면 finally 블록의 실행은 운영 체제가 예외 해제 작업을 트리거하도록 선택했는지 여부에 따라 달라집니다 .

이것은 finally가 예외를 포착하기위한 것이 아니며, catch문 앞에 빈 문 이 있었다면 옳았음을 분명히 보여줍니다 finally.


7

예외가 발생하면 스택은 값을 반환하지 않고 해제되고 (실행은 함수 밖으로 이동) 함수 위의 스택 프레임에있는 catch 블록은 대신 예외를 catch합니다.

따라서 return false실행되지 않습니다.

제어 흐름을 이해하려면 수동으로 예외를 throw합니다.

try {
    command.CommandText = sb.ToString();
    returnValue = command.ExecuteNonQuery();

    // Try this.
    throw new Exception("See where this goes.");

    return returnValue == 1;
} finally {
    command.Dispose();
}

5

코드에서 :

private static bool createRecord(String table, IDictionary<String,String> data, System.Data.IDbConnection conn, OdbcTransaction trans) {

    [... some other code ...]

    int returnValue = 0;
    try {
        command.CommandText = sb.ToString();
        returnValue = command.ExecuteNonQuery();

        return returnValue == 1; // You return here in case no exception is thrown
    } finally {
        command.Dispose(); //You don't have a catch so the exception is passed on if thrown
    }

    return false; // This is never executed because there was either one of the above two exit points of the method reached.
}

finally 블록이 실행 된 다음 false 반환을 실행합니다. 하단에

이것은 finally블록이 예외를 포착하지 않고 마지막 return 문에 도달하지 않기 때문에 논리의 결함입니다 .


4

return falsetry 블록 catch에 예외를 처리 할 부분 이 없기 때문에 마지막 문 에 도달 할 수 없습니다. 따라서 예외는 finally블록 이후에 다시 발생 하고 실행이 마지막 문에 도달하지 않습니다.


2

코드에 두 개의 반환 경로가 있으며 두 번째는 첫 번째로 인해 도달 할 수 없습니다. try블록 의 마지막 문은 return returnValue == 1;정상적인 반환을 제공하므로 return false;메서드 블록의 끝에 도달 할 수 없습니다 .

FWIW, finally블록 과 관련된 실행 순서 는 다음과 같습니다. try 블록에 반환 값을 제공하는 표현식이 먼저 평가되고, finally 블록이 실행 된 다음 계산 된 표현식 값이 try 블록 내부에 반환됩니다.

... 예외없이 유량에 관해서 catch는이 finally예외가 다음 방법 아웃 슬로우 다시 전에 예외에 따라 실행된다; "반환"경로가 없습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.