const 는 컴파일할 때, readonly 는 실행할 때 정해진다
앞에서 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 만 갈아 끼우면 | 안 바뀜 | 바뀜 |
고르는 순서는 이렇게 잡으면 됩니다.
- 정말 영원히 안 바뀌는 값이고 밖에 공개 안 할 것이면
const - 객체마다 다르게 정해져야 하면
readonly - 나머지는
static readonly
정리
const는 컴파일할 때 값이 코드에 박히고 사라집니다. 실행 중에는 그런 자리가 없습니다readonly는 진짜 필드입니다. 생성자에서 채우고 그다음부터 못 바꿉니다const는 컴파일러가 미리 알 수 있는 것만 담습니다.new나DateTime.Now는 안 됩니다- 공개하는 값을
const로 두면 DLL 을 갈아 끼워도 쓰는 쪽은 옛날 값을 씁니다 readonly는 자리를 잠그지 안을 잠그지 않습니다.readonly List에Add는 됩니다
const 는 컴파일하면 값이 코드에 박히고 사라집니다. 컴파일이 지우는 것이 있다는 뜻입니다.