C#에서 예외를 생성하고 throw하는 방법 알아보기
- 16분
.NET은 기본 클래스에서 System.Exception 파생되는 예외 클래스의 계층 구조를 제공합니다. C# 애플리케이션은 모든 예외 형식의 예외를 만들고 throw할 수 있습니다. 개발자는 속성 값을 할당하여 애플리케이션 관련 정보를 사용하여 예외 개체를 사용자 지정할 수도 있습니다.
비고
이 모듈에서는 예외를 만들고 throw하고 예외 개체를 사용자 지정하는 데 중점을 둡니다. 사용자 지정 예외 클래스를 만드는 것은 이 모듈의 범위를 벗어납니다.
예외 개체 만들기
코드 내에서 예외를 만들고 throw하는 것은 C# 프로그래밍의 중요한 측면입니다. 특정 조건, 문제 또는 오류에 대한 응답으로 예외를 생성하는 기능은 애플리케이션의 안정성을 보장하는 데 도움이 됩니다.
만드는 예외 유형은 코딩 문제에 따라 달라지며 가능한 한 예외의 의도된 목적과 일치해야 합니다.
예를 들어 데이터 분석을 수행하는 메서드 GraphData 를 만들고 있다고 가정해 보겠습니다. 이 메서드는 데이터 배열을 입력 매개 변수로 받습니다. 이 메서드는 입력 데이터가 특정 범위에 있을 것으로 예상합니다. 메서드가 예상 범위를 벗어난 데이터를 수신하는 경우 형식 ArgumentException의 예외를 만들고 throw합니다. 예외는 데이터 제공을 담당하는 코드에 의해 호출 스택의 어딘가에서 처리됩니다.
다음은 예외를 만들 때 사용할 수 있는 몇 가지 일반적인 예외 유형입니다.
ArgumentException또는ArgumentNullException: 잘못된 인수 값 또는 null 참조를 사용하여 메서드 또는 생성자를 호출할 때 이러한 예외 형식을 사용합니다.InvalidOperationException: 메서드의 작동 조건이 특정 메서드 호출의 성공적인 완료를 지원하지 않는 경우 이 예외 유형을 사용합니다.NotSupportedException: 작업 또는 기능이 지원되지 않는 경우 이 예외 유형을 사용합니다.IOException: 입력/출력 작업이 실패할 때 이 예외 형식을 사용합니다.FormatException: 문자열 또는 데이터의 형식이 잘못된 경우 이 예외 형식을 사용합니다.
이 new 키워드는 예외 인스턴스를 만드는 데 사용됩니다. 예를 들어 다음과 같이 예외 형식의 인스턴스를 ArgumentException 만들 수 있습니다.
ArgumentException invalidArgumentException = new ArgumentException();
사용자 정의 예외 구성 및 던지기
예외 개체를 throw하는 프로세스에는 예외 파생 클래스의 인스턴스를 만들고, 필요에 따라 예외의 속성을 구성한 다음, 키워드를 사용하여 throw 개체를 throw하는 과정이 포함됩니다.
예외가 throw되기 전에 상황별 정보를 사용하여 예외를 사용자 지정하는 것이 도움이 되는 경우가 많습니다. 해당 속성을 구성하여 예외 개체 내에서 애플리케이션 관련 정보를 제공할 수 있습니다. 예를 들어 다음 코드는 사용자 지정 invalidArgumentException 속성을 사용하여 명명된 Message 예외 개체를 만든 다음 예외를 throw합니다.
ArgumentException invalidArgumentException = new ArgumentException("ArgumentException: The 'GraphData' method received data outside the expected range.");
throw invalidArgumentException;
비고
Message 예외의 속성은 읽기 전용입니다. 따라서 개체를 인스턴스화할 때 사용자 지정 Message 속성을 설정해야 합니다.
예외 개체를 사용자 지정할 때는 문제 및 해결 방법을 설명하는 명확한 오류 메시지를 제공해야 합니다. 사용자가 문제를 해결하는 데 도움이 되는 스택 추적 및 오류 코드와 같은 추가 정보를 포함할 수도 있습니다.
throw 문 내에서 직접 예외 객체를 만들 수도 있습니다. 다음은 그 예입니다.
throw new FormatException("FormatException: Calculations in process XYZ have been cancelled due to invalid data format.");
예외를 throw할 때 유의해야 할 몇 가지 고려 사항은 다음과 같습니다.
- 이 속성은
Message예외의 이유를 설명해야 합니다. 그러나 중요한 정보이거나 보안 문제를 나타내는 정보는 메시지 텍스트에 입력하면 안 됩니다. - 이
StackTrace속성은 예외의 원본을 추적하는 데 자주 사용됩니다. 이 문자열 속성에는 예외와 연결된 각 메서드의 파일 이름 및 줄 번호와 함께 현재 호출 스택에 있는 메서드의 이름이 포함됩니다.StackTrace개체는throw문이 실행되는 시점에서 공용 언어 런타임(CLR)에 의해 자동으로 생성됩니다. 스택 추적이 시작되어야 하는 지점에서 예외를 발생시켜야 합니다.
예외를 발생시키는 경우
메서드는 의도한 목적을 완료할 수 없을 때마다 예외를 throw해야 합니다. throw되는 예외는 오류 조건에 맞는 사용 가능한 가장 구체적인 예외를 기반으로 해야 합니다.
개발자가 비즈니스 프로세스를 구현하는 애플리케이션에서 작업하는 시나리오를 고려합니다. 비즈니스 프로세스는 사용자 입력에 따라 달라집니다. 입력이 예상 데이터 형식과 일치하지 않으면 비즈니스 프로세스를 구현하는 메서드가 예외를 생성하고 발생시킵니다. 예외 개체는 속성 값의 애플리케이션별 정보를 사용하여 구성할 수 있습니다. 다음 코드 샘플에서는 시나리오를 보여 줍니다.
string[][] userEnteredValues = new string[][]
{
new string[] { "1", "two", "3"},
new string[] { "0", "1", "2"}
};
foreach (string[] userEntries in userEnteredValues)
{
try
{
BusinessProcess1(userEntries);
}
catch (Exception ex)
{
if (ex.StackTrace.Contains("BusinessProcess1") && (ex is FormatException))
{
Console.WriteLine(ex.Message);
}
}
}
static void BusinessProcess1(string[] userEntries)
{
int valueEntered;
foreach (string userValue in userEntries)
{
try
{
valueEntered = int.Parse(userValue);
// completes required calculations based on userValue
// ...
}
catch (FormatException)
{
FormatException invalidFormatException = new FormatException("FormatException: User input values in 'BusinessProcess1' must be valid integers");
throw invalidFormatException;
}
}
}
이 코드 샘플에서 최상위 코드 문이 BusinessProcess1 메서드를 호출하고, 사용자가 입력한 값을 포함하는 문자열 배열을 전달합니다. 이 메서드는 BusinessProcess1 정수로 변환할 수 있는 사용자 입력 값을 예상합니다. 메서드가 잘못된 형식의 데이터를 발견하면 사용자 지정된 FormatException 속성을 사용하여 예외 형식의 Message 인스턴스를 만듭니다. 그런 다음 메서드가 예외를 던집니다. 예외는 최상위 문장에서 ex이라는 명명된 개체로 처리됩니다. 개체의 ex 속성은 사용자에게 예외 메시지를 표시하기 전에 검사됩니다. 먼저 코드는 StackTrace 속성을 검사하여 "BusinessProcess1"이 포함되어 있는지 확인합니다. 둘째, 예외 개체 ex 가 형식 FormatException인 것으로 확인됩니다.
예외를 다시 throw하기
새 예외를 throw하는 것 외에도 throw 코드 블록 내부에서 예외를 다시 throw하는 데 catch를 사용할 수 있습니다. 이 경우 throw 예외 피연산자를 사용하지 않습니다.
catch (Exception ex)
{
// handle or partially handle the exception
// ...
// re-throw the original exception object for further handling down the call stack
throw;
}
예외를 다시 throw하면 원래 예외 개체가 사용되므로 예외에 대한 정보가 손실되지 않습니다. 원래 예외를 래핑하는 새 예외 개체를 만들려면 원래 예외를 인수로 새 예외 개체의 생성자에 전달할 수 있습니다. 다음은 그 예입니다.
catch (Exception ex)
{
// handle or partially handle the exception
// ...
// create a new exception object that wraps the original exception
throw new ApplicationException("An error occurred", ex);
}
"BusinessProcess1" 애플리케이션 시나리오의 경우 다음 업데이트를 고려합니다.
BusinessProcess1메서드가 추가 세부 정보를 포함하도록 업데이트되었습니다.BusinessProcess1이제 두 가지 문제가 발생하고 각 문제에 대한 예외를 생성해야 합니다.- 최상위 문장이 업데이트되었습니다. 최상위 문은 이제
OperatingProcedure1메서드를 호출합니다.OperatingProcedure1는BusinessProcess1코드 블록 내에서try를 호출합니다. - 메서드는
OperatingProcedure1예외 형식 중 하나를 처리하고 다른 형식을 부분적으로 처리할 수 있습니다. 부분적으로 처리된 예외가 처리되면OperatingProcedure1원래 예외를 다시 throw해야 합니다.
다음 코드 샘플에서는 업데이트된 시나리오를 보여 줍니다.
try
{
OperatingProcedure1();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Exiting application.");
}
static void OperatingProcedure1()
{
string[][] userEnteredValues = new string[][]
{
new string[] { "1", "two", "3"},
new string[] { "0", "1", "2"}
};
foreach(string[] userEntries in userEnteredValues)
{
try
{
BusinessProcess1(userEntries);
}
catch (Exception ex)
{
if (ex.StackTrace.Contains("BusinessProcess1"))
{
if (ex is FormatException)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Corrective action taken in OperatingProcedure1");
}
else if (ex is DivideByZeroException)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Partial correction in OperatingProcedure1 - further action required");
// re-throw the original exception
throw;
}
else
{
// create a new exception object that wraps the original exception
throw new ApplicationException("An error occurred - ", ex);
}
}
}
}
}
static void BusinessProcess1(string[] userEntries)
{
int valueEntered;
foreach (string userValue in userEntries)
{
try
{
valueEntered = int.Parse(userValue);
checked
{
int calculatedValue = 4 / valueEntered;
}
}
catch (FormatException)
{
FormatException invalidFormatException = new FormatException("FormatException: User input values in 'BusinessProcess1' must be valid integers");
throw invalidFormatException;
}
catch (DivideByZeroException)
{
DivideByZeroException unexpectedDivideByZeroException = new DivideByZeroException("DivideByZeroException: Calculation in 'BusinessProcess1' encountered an unexpected divide by zero");
throw unexpectedDivideByZeroException;
}
}
}
업데이트된 샘플 코드는 다음 출력을 생성합니다.
FormatException: User input values in 'BusinessProcess1' must be valid integers
Corrective action taken in OperatingProcedure1
DivideByZeroException: Calculation in 'BusinessProcess1' encountered an unexpected divide by zero
Partial correction in OperatingProcedure1 - further action required
DivideByZeroException: Calculation in 'BusinessProcess1' encountered an unexpected divide by zero
Exiting application.
예외를 발생시킬 때 피해야 할 사항
다음 목록에서는 예외를 throw할 때 방지해야 하는 사례를 식별합니다.
- 예외를 사용하여 프로그램의 흐름을 일반 실행의 일부로 변경하지 마세요. 예외를 사용하여 오류 조건을 보고하고 처리합니다.
- 예외가 throw되는 대신 반환 값이나 매개 변수로 반환되어서는 안 됩니다.
- 자신의 소스 코드에서
System.Exception,System.SystemException,System.NullReferenceException또는System.IndexOutOfRangeException를 의도적으로 throw하지 마세요. - 디버그 모드에서 throw될 수 있지만 릴리스 모드에서는 throw할 수 없는 예외를 만들지 마세요. 개발 단계에서 런타임 오류를 식별하려면 대신 사용합니다
Debug.Assert.
비고
이 Debug.Assert 메서드는 개발 중에 논리 오류를 찾기 위한 도구입니다. 기본적으로 이 메서드는 Debug.Assert 디버그 빌드에서만 작동합니다. 디버그 세션에서 사용하면 Debug.Assert 절대 발생하지 않아야 하는 조건을 확인할 수 있습니다. 이 메서드는 검사할 부울 조건과 조건이 false있는지 표시할 선택적 문자열 메시지라는 두 개의 매개 변수를 사용합니다. Debug.Assert 은 코드의 정상적인 실행 중에 발생하는 예외적인 상황을 처리하기 위해 예외를 throw하는 것을 대신해서는 안 됩니다. 절대 발생해서는 안 되는 오류를 Debug.Assert을 사용하여 catch하고, 예외를 사용하여 프로그램의 정상적인 실행 중에 발생할 수 있는 오류를 처리해야 합니다.
요약
이 단원에서 기억해야 하는 몇 가지 중요한 사항은 다음과 같습니다.
- 예외를 만들고 throw할 때 예외 형식은 가능한 한 예외의 의도한 목적과 일치해야 합니다.
throw예외를 처리하려면 예외 파생 클래스의 인스턴스를 만들고 해당 속성을 구성한 다음throw키워드를 사용해야 합니다.- 예외 개체를 만들 때 사용자가 문제를 해결하는 데 도움이 되도록 명확한 오류 메시지와 추가 정보를 제공하는 것이 중요합니다.