.NET의 BCL에서는 문자열을 숫자로 변환하는 세가지 메서드를 지원한다.
각 메서드의 특징에 대해 정리해보자.
(숫자를 표현하는 여러 자료형이 있지만 해당 포스트는 Int32를 기준으로 정리한다.)
// Parse 메서드 정의
namespace System
{
// ...
public static int Parse(string s)
{
if (s == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
return Number.ParseInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo);
}
// ...
}
Parse 메서드의 정의를 살펴보면 한가지 알 수 있는 점이 있다.
바로 매개변수가 null인 경우 예외를 발생시킨다는 것이다.
즉 이 메서드는 매개변수의 값에 따라 예외를 발생시킬 위험이 있다.
그럼 매개변수로 값을 넘기기 전에 null인지 확인만 하면 괜찮지 않을까?
string digits = "12321a"; // 정수형이 아닌 값
try
{
int num = int.Parse(digits);
Console.WriteLine(num);
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
digits = "2147483648"; // 4바이트 자료형의 범위를 넘어가는 수
try
{
int num = int.Parse(digits);
Console.WriteLine(num);
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
System.FormatException: Input string was not in a correct format.
at System.Number.ThrowOverflowOrFormatException(ParsingStatus status, TypeCode type)
at System.Int32.Parse(String s)
at Program.Main(String[] args) in C:\Users\wjdgh9577\Desktop\TestProject\TestProject\Program.cs:line 12
System.OverflowException: Value was either too large or too small for an Int32.
at System.Number.ThrowOverflowOrFormatException(ParsingStatus status, TypeCode type)
at System.Int32.Parse(String s)
at Program.Main(String[] args) in C:\Users\wjdgh9577\Desktop\TestProject\TestProject\Program.cs:line 24
숫자로 변환할 수 없는 값에 대해서는 FormatException을, 숫자로 변환 가능한 범위를 넘어간 경우 OverflowException을 발생시킨다.
이를 통해 Parse 메서드는 매개변수를 넘기는데 주의가 필요하며 별도의 예외 처리를 동반해야 된다는 것을 알 수 있다.
// TryParse 메서드 정의
// Parses an integer from a String. Returns false rather
// than throwing an exception if input is invalid.
//
public static bool TryParse([NotNullWhen(true)] string? s, out int result)
{
if (s == null)
{
result = 0;
return false;
}
return Number.TryParseInt32IntegerStyle(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo, out result) == Number.ParsingStatus.OK;
}
TryParse 메서드는 특이하게 참조에 의한 호출인 out 예약어를 통해 변환된 값을 반환하고 메서드는 변환 성공 여부를 bool 자료형으로 반환한다.
그리고 메서드의 설명을 보면, Parse와는 다르게 변환에 실패할 경우 예외를 throw하지 않고 false를 반환한다.
try/catch문을 사용하지 않고 좀 더 우리에게 친숙한 형태의 코드를 작성할 수 있을 것 같다.
// ToInt32 메서드 정의
public static int ToInt32(string? value)
{
if (value == null)
return 0;
return int.Parse(value);
}
정의를 보면 알겠지만 Convert.ToInt32는 그저 int.Parse 앞에 유효성 검사가 추가된 것일 뿐이다.
즉 매개변수가 null인 경우에 한해서, Convert.ToInt32는 0을 반환하고 int.Parse는 예외를 발생시킨다는 차이가 있을 뿐 둘은 거의 같은 기능을 한다.
string digits = null;
try
{
int num = Convert.ToInt32(digits);
Console.WriteLine(num);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
try
{
int num = int.Parse(digits);
Console.WriteLine(num);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
0
System.ArgumentNullException: Value cannot be null. (Parameter 's')
at System.Int32.Parse(String s)
at Program.Main(String[] args) in C:\Users\wjdgh9577\Desktop\TestProject\TestProject\Program.cs:line 22
Parse 메서드를 사용한다면 반환할 자료형에 따라 우리는 각각의 자료형 구조체에 접근해서 Parse 메서드를 호출해야 한다.
하지만 Convert 클래스는 해당 기능을 오버로드 메서드로 제공하기 때문에 사용하기가 간편하다는 이점이 있다.
이전 포스트에서도 언급했지만 예외 처리의 오버헤드는 생각보다 크기 때문에 남용해선 안된다.
아래에 간단한 실험용 코드가 있다.
Stopwatch sw = new Stopwatch();
string digits = "12321"; // 입력이 올바른 경우
sw.Start();
for (int i = 0; i < 1000; i++)
{
try
{
int num = int.Parse(digits);
}
catch
{
}
}
sw.Stop();
Console.WriteLine("Correct Parsing(Parse): " + sw.Elapsed.TotalMilliseconds + "ms");
sw.Restart();
for (int i = 0; i < 1000; i++)
{
if (int.TryParse(digits, out int num))
{
}
}
sw.Stop();
Console.WriteLine("Correct Parsing(TryParse): " + sw.Elapsed.TotalMilliseconds + "ms");
digits = "12321a"; // 입력이 잘못된 경우
sw.Restart();
for (int i = 0; i < 1000; i++)
{
try
{
int num = int.Parse(digits);
}
catch
{
}
}
sw.Stop();
Console.WriteLine("Incorrect Parsing(Parse): " + sw.Elapsed.TotalMilliseconds + "ms");
sw.Restart();
for (int i = 0; i < 1000; i++)
{
if (int.TryParse(digits, out int num))
{
}
}
sw.Stop();
Console.WriteLine("Incorrect Parsing(TryParse): " + sw.Elapsed.TotalMilliseconds + "ms");
Correct Parsing(Parse): 0.5463ms
Correct Parsing(TryParse): 0.0183ms
Incorrect Parsing(Parse): 4348.5997ms
Incorrect Parsing(TryParse): 0.0175ms
입력값이 잘못된 경우 1000번의 반복 수행에서 int.Parse는 1000번의 예외를 발생시켜 실행 시간이 어마어마하게 뻥튀기 되었다.
반면에 int.TryParse는 입력값에 상관없이 일정한 퍼포먼스를 보여준다.
(Convert.ToInt32는 int.Parse로 정의됐기 때문에 굳이 코드에 추가하진 않았다. 실제로 성능 차이도 없었다.)
특별히 꼭 써야 되는 이유가 있는게 아니라면 TryParse를 사용하는 것이 좋을 것 같다.
참고 자료
시작하세요! C# 10 프로그래밍 - 정성태