"123" 은 숫자 123 이 아니다
Esc

    "123" 은 숫자 123 이 아니다

    –C#기초

    나이를 입력받아 한 살을 더해 보겠습니다.

    Console.Write("나이: ");
    string age = Console.ReadLine();     // 20 을 입력했습니다
    
    Console.WriteLine(age + 1);          // 201

    21 이 아니라 201 입니다. 오타도 버그도 아니고, 코드가 시킨 대로 한 것입니다.

    "123" 안에 들어 있는 것

    앞에서 글자마다 번호가 붙어 있다고 했습니다. 문자 '1' 의 번호는 49 입니다. '0' 이 48이니 그다음입니다.

    그러니까 문자열 "123" 이 메모리에 들고 있는 것은 이렇습니다.

    string "123" '1' '2' '3' 49 50 51 칸 세 개, char 하나가 2바이트니 6바이트 int 123 0000 0000 ... 0111 1011 글자 세 개가 나란히 놓인 것입니다. 계산에 쓸 수 있는 수가 아닙니다. 숫자 하나가 2진수로 들어 있습니다. 더하기 곱하기가 되는 것은 이쪽뿐입니다.
    같은 123 처럼 보여도 메모리에 들어 있는 비트가 아예 다릅니다.

    앞에서 이렇게 정리했습니다. 메모리에는 비트만 있고, 그걸 무엇으로 읽을지는 타입이 정한다. "123" 과 123 은 같은 것을 다르게 읽는 것도 아니고, 아예 다른 비트가 들어 있는 것입니다.

    + 는 양쪽을 보고 할 일을 정한다

    앞에서 연산자를 다룰 때 + 가 두 가지 일을 한다고 했습니다. 어느 쪽인지는 양옆에 무엇이 있느냐로 정해집니다.

    Console.WriteLine(1 + 2);       // 3     양쪽이 숫자면 덧셈
    Console.WriteLine("1" + "2");   // 12    양쪽이 문자열이면 이어붙이기
    Console.WriteLine("20" + 1);    // 201   한쪽이 문자열이면 이어붙이기

    세 번째가 처음 예제의 정체입니다. 한쪽이 문자열이면 다른 쪽도 문자열로 바꿔서 이어붙입니다. 1 이 "1" 이 되고, "20" 뒤에 붙어 "201" 이 됩니다.

    에러가 안 나기 때문에 더 골치입니다. 앞에서 본 좁은 칸으로 가는 변환은 컴파일러가 막아 세우기라도 하는데, 이건 조용히 되고 결과만 이상합니다.

    입력은 언제나 문자열이다

    Console.ReadLine() 은 무엇을 입력하든 string 을 돌려줍니다. 숫자를 입력했다고 숫자가 되지 않습니다. 파일에서 읽거나 통신으로 받은 것도 마찬가지입니다.

    계산하려면 바꿔야 합니다. 바꾸려는 타입 이름 뒤에 Parse 를 붙입니다.

    int n = int.Parse("123");
    double d = double.Parse("3.14");
    bool flag = bool.Parse("true");

    Parse 는 못 바꾸면 터진다

    문제는 바꿀 수 없는 값이 들어왔을 때입니다.

    int.Parse("abc");            // FormatException
    int.Parse("");               // FormatException
    int.Parse("3.5");            // FormatException    int 자리에 소수점
    int.Parse("99999999999");    // OverflowException  int 범위를 넘음

    마지막 것은 앞에서 본 정수의 범위와 이어집니다. int 가 담을 수 있는 범위 밖이라 조용히 한 바퀴 도는 게 아니라 예외로 알려줍니다. 캐스팅과 다른 점입니다. (int) 는 잘라내고 넘어가지만 Parse 는 멈춥니다.

    예외가 나면 프로그램이 그 자리에서 죽습니다. 코드를 짠 사람 입장에서는 확실해서 좋지만, 사용자가 오타 한 번 냈다고 프로그램이 종료되면 안 됩니다.

    TryParse 는 알려주기만 한다

    같은 일을 하되 실패했을 때 행동이 다릅니다. 터지는 대신 false 를 돌려줍니다.

    out int n 이 낯설 텐데, “결과를 담을 변수를 여기 줄 테니 채워 달라” 는 뜻입니다. TryParse 가 돌려주는 값은 성공했는지 여부(bool)라서, 바꾼 숫자를 받을 자리가 따로 필요합니다. out 이 정확히 어떻게 동작하는지는 메서드를 다룰 때 봅니다.

    주의할 게 하나 있습니다. 실패하면 n 에 0 이 들어갑니다. 그래서 n == 0 으로 실패를 판단하면 안 됩니다. 사용자가 진짜 0 을 입력한 경우와 같아집니다. 판단은 항상 돌려받은 bool 로 합니다.

    될 때까지 다시 묻기

    TryParse 가 진가를 발휘하는 자리는 앞에서 본 반복문과 만날 때입니다.

    int n;
    
    while (!int.TryParse(Console.ReadLine(), out n))
    {
        Console.WriteLine("숫자만 입력해주세요.");
    }
    
    Console.WriteLine($"{n} 의 두 배는 {n * 2}");

    바꾸는 데 실패하는 동안 계속 다시 묻습니다. 성공하면 조건이 false 가 되어 빠져나오고, 그 시점에 n 에는 제대로 된 숫자가 들어 있습니다. Parse 로는 이렇게 못 씁니다. 첫 오타에서 이미 프로그램이 없습니다.

    정리하면 기준은 이렇습니다.

    바깥에서 들어온 값은 TryParse, 코드 안에서 만든 확실한 값은 Parse.

    사용자 입력, 파일, 통신으로 받은 것은 전부 바깥에서 온 값입니다.

    반대 방향은 그냥 된다

    숫자를 문자열로 바꾸는 것은 실패할 일이 없어서 TryToString 같은 게 없습니다.

    int n = 1234567;
    
    string a = n.ToString();          // "1234567"
    string b = $"{n}";                // "1234567"   앞에서 본 보간
    string c = n.ToString("N0");      // "1,234,567" 형식 지정자도 그대로 쓸 수 있습니다

    앞에서 본 형식 지정자를 ToString 에 그대로 넘길 수 있습니다. 보간의 콜론 뒤에 적던 것과 같은 문자열입니다.

    정리

    • 입력으로 받은 것은 언제나 문자열이다. 숫자를 입력해도 숫자가 되지 않는다.
    • "123" 은 문자 세 개의 번호를 담고 있고, 123 은 2진수 하나를 담고 있다. 다른 비트다.
    • + 는 한쪽이 문자열이면 이어붙인다. "20" + 1 은 "201" 이다.
    • 바꾸려는 타입 이름 뒤에 Parse 를 붙인다. int.Parse, double.Parse.
    • Parse 는 못 바꾸면 예외를 던진다. 범위를 넘겨도 예외다. 캐스팅처럼 잘라내지 않는다.
    • TryParse 는 터지는 대신 false 를 돌려주고, 결과는 out 변수에 담아 준다.
    • 실패하면 out 변수에 0 이 들어간다. 성공 여부는 반드시 돌려받은 bool 로 판단한다.
    • 바깥에서 들어온 값은 TryParse, 코드 안에서 만든 확실한 값은 Parse.