const 는 컴파일할 때, readonly 는 실행할 때 정해진다
Esc

    const 는 컴파일할 때, readonly 는 실행할 때 정해진다

    –C#

    앞에서 const 를 봤습니다. 숫자를 코드에 그냥 박아 놓지 말고 이름을 붙이자는 이야기였습니다.

    const int 최대체력 = 100;

    그때 안 물어본 것이 있습니다. 이 값은 언제 정해지나요.

    값을 못 바꾸게 막는 방법이 하나 더 있는데, 그 답이 서로 다릅니다.

    const 는 컴파일할 때 값이 코드에 박힙니다. readonly 는 실행하다가 생성자에서 정해집니다.

    readonly 는 이렇게 생겼다

    필드 앞에 붙입니다. 여기까지는 const 와 같아 보입니다.

    class 캐릭터
    {
        public readonly string 이름;
        public const int 최대레벨 = 99;
    
        public 캐릭터(string 이름)
        {
            this.이름 = 이름;   // 생성자에서는 넣을 수 있습니다
        }
    }

    두 가지가 눈에 띕니다.

    readonly 는 선언할 때 값을 안 줘도 됩니다. 생성자에서 채우면 됩니다. const 는 선언하는 자리에서 값을 줘야 합니다. 생성자에서 못 채웁니다.

    이건 문법 취향이 아니라 정해지는 시점이 달라서 어쩔 수 없는 것입니다. 생성자는 실행할 때 도는 코드인데, const 는 실행하기 전에 이미 값이 있어야 하니까요.

    const 는 실행할 때 존재하지 않는다

    const 는 변수가 아닙니다. 컴파일러가 그 자리에 값을 그대로 써 넣고 사라집니다.

    읽어 오는 일 자체가 없으니 빠릅니다. 대신 값을 바꿀 방법도 없습니다. 실행 중에 바꾸고 말고가 아니라 바꿀 대상이 아예 없기 때문입니다.

    readonly 는 반대입니다. 진짜 필드가 하나 있고, 거기 값이 들어 있고, 생성자가 끝난 뒤부터 못 씁니다.

    그래서 담을 수 있는 것이 다르다

    컴파일할 때 값이 정해져야 한다는 조건이 const 를 크게 제한합니다.

    const int a = 10;                        // 됩니다
    const string b = "안녕";                  // 됩니다
    const double c = 3.14 * 2;               // 됩니다. 계산도 컴파일러가 미리 합니다
    
    const DateTime d = DateTime.Now;         // 에러
    const int[] e = new int[3];              // 에러
    const 점 f = new 점(1, 2);                // 에러

    DateTime.Now 는 실행해 봐야 압니다. new 는 실행하면서 자리를 잡습니다. 컴파일러가 미리 알 수 없으니 const 가 될 수 없습니다.

    const 로 쓸 수 있는 것은 숫자, bool, char, string, enum 뿐입니다. 나머지는 전부 readonly 로 가야 합니다.

    public static readonly DateTime 시작시각 = DateTime.Now;   // 됩니다
    public static readonly int[] 기본값 = { 1, 2, 3 };         // 됩니다

    남의 프로젝트에서 사고가 나는 자리

    이게 const 의 진짜 함정입니다.

    라이브러리 하나를 만들어서 const int 버전 = 1; 을 넣고 배포했다고 해 봅시다. 다른 프로젝트가 그 라이브러리를 참조해서 Console.WriteLine(라이브러리.버전) 을 씁니다.

    그쪽 코드가 컴파일될 때 1 이 그대로 박힙니다.

    이제 라이브러리를 고쳐 const int 버전 = 2; 로 바꾸고 새 DLL 만 갈아 끼웁니다. 쓰는 쪽은 다시 컴파일하지 않았습니다.

    여전히 1 이 찍힙니다. DLL 은 2 인데 쓰는 쪽 코드에는 1 이 박혀 있으니까요.

    readonly 였다면 실행할 때 DLL 에서 읽어 오므로 2 가 나왔을 겁니다.

    그래서 규칙이 하나 나옵니다. 다른 프로젝트에서 참조할 수 있는 공개 값은 const 로 두지 않습니다. 원주율처럼 영원히 안 바뀔 것만 const 이고, 나머지는 static readonly 입니다.

    readonly 는 상자를 잠그지 안을 잠그지 않는다

    두 번째 함정입니다. 이건 값 타입과 참조 타입을 알면 저절로 설명됩니다.

    public readonly List<string> 기록 = new List<string>();
    
    기록.Add("첫 줄");          // 됩니다
    기록 = new List<string>();  // 에러

    붙이는 것은 되고 갈아 끼우는 것은 안 됩니다. 뒤집힌 것처럼 보이지만 규칙은 하나입니다.

    readonly 가 잠그는 것은 그 변수 자리입니다. 참조 타입 변수 안에 든 것은 주소였습니다. 그러니 잠긴 것은 주소입니다. 그 주소가 가리키는 리스트 안은 아무도 안 잠갔습니다.

    값 타입이면 안까지 같이 잠깁니다. 자리 안에 값이 통째로 들어 있으니 잠글 것이 그것뿐이기 때문입니다.

    내용까지 못 바꾸게 하려면 타입 자체를 바꿔야 합니다.

    public readonly IReadOnlyList<string> 기록 = new List<string>();

    언제 무엇을 쓰나

    const readonly
    정해지는 시점 컴파일할 때 생성자가 끝날 때
    실행 중 존재 안 함 (값이 박힘) 함 (진짜 필드)
    담을 수 있는 것 숫자, bool, char, string, enum 전부
    생성자에서 넣기 안 됨 됨
    객체마다 다르게 안 됨 됨
    DLL 만 갈아 끼우면 안 바뀜 바뀜

    고르는 순서는 이렇게 잡으면 됩니다.

    1. 정말 영원히 안 바뀌는 값이고 밖에 공개 안 할 것이면 const
    2. 객체마다 다르게 정해져야 하면 readonly
    3. 나머지는 static readonly

    정리

    • const 는 컴파일할 때 값이 코드에 박히고 사라집니다. 실행 중에는 그런 자리가 없습니다
    • readonly 는 진짜 필드입니다. 생성자에서 채우고 그다음부터 못 바꿉니다
    • const 는 컴파일러가 미리 알 수 있는 것만 담습니다. new 나 DateTime.Now 는 안 됩니다
    • 공개하는 값을 const 로 두면 DLL 을 갈아 끼워도 쓰는 쪽은 옛날 값을 씁니다
    • readonly 는 자리를 잠그지 안을 잠그지 않습니다. readonly List 에 Add 는 됩니다

    const 는 컴파일하면 값이 코드에 박히고 사라집니다. 컴파일이 지우는 것이 있다는 뜻입니다.