이 글은 제가 과거에 운영했던 사이트인 http://dotnet.mkexdev.net 의 글을 옮겨온 것입니다. 원본 글은 2010년에 작성되었습니다.
그 전에 운영했었던 사이트(mkex.pe.kr)은 흔적도 없이 사라 졌습니다. 그속의 글들도 모두... 그래서 이 사이트도 사라지기 전에 옮기고 싶은 글을 조금씩 이 블로그로 이동시키려 합니다.
(원본글) http://dotnet.mkexdev.net/Article/Content.aspx?parentCategoryID=1&categoryID=5&ID=677
이 글은 닷넷 기초 지식을 전파하기 위해 2010경에 작성되었으며, 당시 윤성우의 프로그래밍 스터디그룹 네이버 카페에도 필진으로 참여하여 연재했던 글이기도 합니다. 현재 시점에서 조금 달라진 부분이 있을 수 있으나 기본 원리와 언어 기초에 해당하는 부분은 크게 변하지 않았을 것으로 생각하며 이런 부분들을 감안해서 봐 주시기 바랍니다.
“상수 선언, const 와 readonly”
안녕하세요. 박종명입니다. 닷넷 열세 번째 강좌를 진행하도록 하겠습니다.
이번 강좌는 C#에서 상수를 선언하는 키워드인 const 와 readonly 에 대해 알아 보겠습니다.
상수 선언에 있어 왜 두 가지 키워드를 제공해 주는 걸까요?
두 키워드의 설명과 사용법 그리고 차이점에 대해 하나씩 살펴보도록 하겠습니다
‘변하는 수’ 혹은 ‘변경 가능한 수’를 변수라 합니다. 프로그램을 개발할 때 필수로 사용되는 한 요소이죠.
상수는 그 반대 입니다. 상수는 ‘변하지 않는 수’, ‘변경할 수 없는 수’를 말합니다.
영어로 ‘변함없는’ 이라는 뜻을 가진 constant 로 해석되며 한자로는 항상 상(常)자를 이용하여 상수(常數) 라 합니다.
어떤 수를 한번 정의 해 두면 더 이상 변경할 필요가 없는 것을 상수로 선언해 사용하게 되는데요.
대표적으로 이러한 류의 데이터 예로 원주율인 π(파이) 를 들 수 있겠습니다.
private const PI = 3.14;
원주율 값이 정해져 있기 때문에 한번 그 값이 설정되면 프로그램 수행 도중 다른 곳에서 수정할 이유가 없는 것이죠.
따라서 상수로 사용하는 좋은 예입니다
이처럼 변하지 않는 원주율 값을 상수로 선언해 두면 이 값을 참조하기 위해 어느 곳에서든 PI 를 이용할 수 있습니다.
물론 이 경우 상수로 선언하지 않아도 원주율이 필요한 곳에서 3.14 란 값을 직접 사용할 수도 있습니다.
그러나 프로그램의 직관성 및 효율성을 위해 의미 있는 이름의 상수 선언을 권장합니다.
const
영어로 상수란 뜻을 가진 단어인 constant의 표현 그대로가 사용된 대표적인 상수 선언 키워드 입니다.
간단히 아래와 같이 상수 값을 선언할 수 있습니다
class MyClass{
public const int X = 1;
}
const 상수의 하나씩 살펴 보도로 하겠습니다.
const 상수는 반드시 선언 시 그 값을 할당해야 한다
일반적인 멤버 변수는 선언 시 값을 할당하지 않아도 자료형에 따른 기본 값이 자동 할당됩니다.
반면 const 상수는 선언 시 반드시 그 값을 할당(초기화)해야 합니다.
const 상수는 한번 값이 할당되면 이후 변경 불가능하다
당연한 예기죠. 상수니깐요. 당연한 것을 굳이 설명하는 이유는 이후 알아볼 readonly의 차이점 때문입니다.
const 상수는 자동으로 static 이다
const 로 선언한 상수는 자동으로 static 변수가 됩니다. 즉 정적 상수의 역할을 하게 되는 것이죠.
다음과 같이 const 상수와 일반 변수를 선언하고 IL코드를 보면 const 상수는 자동으로 static 키워드가 추가된 것을 확인할 수 있습니다
class MyClass{
public const int X = 1;
public int Y = 1;
}
자동으로 static 이기 때문에 코드에서 명시적으로 static 를 지정할 수 없습니다.
public static const int X = 1; //컴파일 오류 발생
참조 형식 상수 선언
참조 타입은 const 일 수 없습니다. 다만 string 객체는 가능합니다.
엄밀히 말하면 참조 타입이 const일 수 없다기 보다는 const 로 선언된 참조 타입의 값은 null 일수 밖에 없다는 것입니다.
동적으로 메모리에 할당되는 객체와 같은 참조타입을 const 로 선언하여 초기화 할 수 없습니다
예를 들어 다음과 같이 배열(참조타입)을 const로 선언하여 초기화 할 수 없습니다
public const int[] array = new int[] { 1, 2 }; //컴파일 오류 발생
이는 const 상수의 값은 반드시 컴파일 시점에 결정되어야 하는, 다시 말해 컴파일 타임에 완전하게 계산될 수 있는 값이어야 하기 때문입니니다. 컴파일러는 const 상수를 컴파일 시 그 값을 어셈블리의 메타데이터에 바로 기록하도록 합니다.
그러나 한가지 예외가 있는데요. 바로 문자열 객체입니다.
문자열(string) 역시 참조타입 이지만 const 로 선언할 수 있습니다. 문자열은 컴파일 시 그 값을 메타데이터에 정확히 기록할 수 있기 때문에 상수로 사용이 가능합니다
public const string S = "MKEX"; //사용 가능함
더불어 참조 형식을 const 로 선언할 경우 null로 초기화는 가능합니다.
public const MyClass myClass = null; //사용 가능함
그러나 상수는 이후에 변경이 불가능하기 때문에 이러한 코드는 무의미 할 수 있습니다. 따라서 참조타입 상수화는 문자열을 제외하고는 잘 사용하지 않습니다.
const 상수는 참조형태로 전달할 수 없습니다
앞의 내용과 연관되는 부분인데요.
const 상수는 ref 나 out 키워드를 사용하여 참조형태로 값을 전달할 수 없습니다.
앞서 설명한대로 const 상수는 컴파일 시 메타데이터에 그 값이 직접 기록되기 때문에 런타임 시에 이 상수에 대한 메모리 할당은 이루어지지 않습니다. 따라서 주소 값을 기반으로 하는 ref와 같은 참조형태로 값을 전달할 수 없는 것입니다.
상수를 선언하고 그 상수를 사용할 경우 IL 코드를 보겠습니다
다음과 같이 const 상수 X가 10으로 초기화 되어 있고…
class MyClass{
public const int X = 10;
}
이 상수 X를 사용하는 다음 어셈블리의 IL코드를 보면 아래 그림과 같습니다.
class Program {
static void Main(string[] args){
Console.WriteLine(MyClass.X);
}
}
Main 메서드의 메타데이터인데요,
상수 X의 실제 값인 10 이 메타데이터로 바로 기록된 것을 확인할 수 있습니다.
상수 선언에 다른 상수를 사용할 수 있습니다.
다음 코드와 같이 상수를 선언할 때 이미 정의된 다른 상수를 대입하여 상수식을 만들 수 있습니다.
class MyClass{
public const int X = 1;
public const int Y = X * 2;
}
readonly
닷넷은 상수 선언을 위해 const 외에 readonly 라는 키워드도 추가로 제공합니다.
상수면 상수지 왜 두 가지 버전의 상수를 선언하도록 하는 것일까요?
그것은 바로 상수 값의 초기화 방법에 대한 확장을 주기 위함입니다.
앞서 const 는 선언과 동시에 값을 할당, 즉 초기화 해야 한다고 했습니다.
그러나 readonly는 여기에 더불어 생성자에서 한번 더 값을 할당할 수 있는 확장성을 제공합니다.
즉 readonly 상수는 선언할 때 값을 할당할 수도 있으며 생성자에도 할당할 수 있습니다.
다음코드처럼 readonly 상수인 X는 선언 시 초기화 하고 더불어 생성자에서도 그 값을 변경할 수 있습니다.
class MyClass{
public readonly int X = 1;
public MyClass(int i){
X = i; //생성자를 통해 상수 읽기전용 X의 값을 할당한다
}
}
결과적으로 readonly 상수의 값을 생성자에 따라 다르게 구현할 수 있는 확장성이 제공되는 것입니다.
그래서 const 를 컴파일 타임 상수라고 하며 readonly 를 런타임 상수라고 부르기도 합니다.
readonly 상수의 특징을 하나씩 살펴 보도로 하겠습니다.
readonly 상수는 선언 시 값을 할당하지 않아도 됩니다.
const 상수는 선언과 동시에 값을 초기화해야 하지만 readonly 상수는 선언 시 초기화가 의무는 아닙니다.
즉 readonly 상수를 선언할 때 값을 할당하지 않으면 클래스 멤버변수와 같이 타입에 맞는 기본 값이 자동 할당됩니다.
readonly 상수는 생성자에서 한번 더 그 값을 변경할 수 있습니다.
const 는 선언 시 할당된 값은 이후 변경이 불가능한 반면 readonly 상수는 선언 시 값을 초기화 했다고 하더라도 생성자에서 한번 더 그 값을 변경할 수 있습니다. 이것이 const 와의 핵심적인 차이라 하겠습니다.
readonly 상수는 static 이 아닙니다
const 는 자동으로 static 이지만 readonly 는 그렇지 않습니다. const 가 클래스 상수라면 readonly 는 객체 상수라고 할 수 있습니다.
즉 readonly 상수는 클래스의 인스턴스로 생성된 객체를 통해서 접근할 수 있습니다.
아래 IL코드를 보면 readonly 로 선언된 상수 X는 static 아 아님을 알 수 있습니다.
다만 readonly 상수를 정적 상수로 만들고 싶으면 static 키워드를 명시해 주면 됩니다.
만일 readonly 를 static키워드와 같이 사용하여 정적 상수로 만들 경우에는 일반 생성자가 아닌 정적 생성자를 통해서만 값을 변경할 수 있습니다
다음 코드를 보면 X 가 정적 상수로 선언되어 있습니다. 그리고 정적 생성자를 통해 그 값을 한번 더 변경하도록 합니다.
class MyClass{
public static readonly int X = 1;
static MyClass()
{
X = 2; //static로 선언된 readonly상수는 static 생성자를 통해 값을 변경할 수 있다다
}
}
참조 형식 상수 선언
const 에서는 문자열(string)과 null을 제외하고는 참조 타입을 초기화 할 수 없는 반면 readonly는 참조타입의 객체를 생성할 수 있습니다. 즉 다음과 같은 선언이 가능합니다.
public readonly int[] array = new int[] { 1, 2 };
따라서 readonly는 상수라는 표현보다는 읽기전용 필드라고 표현하는 것이 일반적입니다.
한가지 예외, 리플렉션
readonly 로 선언된 읽기전용 필드는 선언 시 한번 그리고 생성자에서 한번 해서 총 두 지점에서만 값의 초기화가 가능하다고 하였습니다.
그러나 리플렉션 기법을 이용하면 실행 중에도 이 값을 변경할 수 있습니다.이것은 예외 적인 상황이라 할 수 있는데요. 사실 리플렉션은 일반적인 클래스 사용 패턴이 아니기 때문에 굳이 예외적이라기 보다는 특수한 형태라는 표현이 더 맞겠네요. 리플렉션에 관해서는 다음 기회에 알아 보도록 하겠습니다.
const 보다는 readonly를 사용하라???
닷넷 개발자들이 자주 하는 말입니다. ‘상수를 사용할 거면 const 보다는 readonly를 사용하라’ 입니다.
이것은 다중 어셈블리환경, 어셈블리 간 참조로 구성된 프로그램일 경우 어셈블리 버전 문제에 해당되는 내용입니다.
만일 A라는 어셈블리에 const 상수가 존재하고 B라는 어셈블리에서 A를 참조해서 상수를 사용하는 경우에 상수 값이 변경되어 A 어셈블리를 다시 컴파일 해서 배포할 경우 B 어셈블리에서는 이 내용이 갱신되지 않는 문제가 발생합니다. 즉 버전 변경에 따른 예기치 않은 결과가 나타나는 것이죠.
이 경우 A 어셈블리뿐만 아니라 이를 참조하는 B 어셈블리도 같이 재 컴파일 해야만 문제를 해결할 수 있습니다.
이것은 앞서 설명한대로 const 상수는 이를 사용하는 어셈블리의 메타데이터로 그 값이 직접 기록되기 때문에 발생하는 문제입니다. 즉 A 어셈블리는 외부어셈블리이지만 B 어셈블리의 메타데이터로 상수 값 자체가 기록되어 버리기 때문에 A 어셈블리의 버전 업이 B어셈블리의 메타데이터를 변경하지는 못하는 것입니다.
만일 A 어셈블리의 상수가 readonly 필드로 정의되어 있다면 이러한 문제로부터 자유로워 집니다.
즉 readonly 필드의 값은 B 어셈블리의 메타데이터에 바로 기록되는 것이 아니라 외부 어셈블리를 참조하도록 기록되기 때문에 A어셈블리의 재 컴파일 만으로도 변경된 값이 적용되는 것입니다.
이런 이유로 해서 ‘const 보다는 readonly를 사용하라’ 라고 하는 것입니다.
만일 이런 상황이 아니라면 굳이 readonly 가 더 좋을 이유는 없습니다.
자세한 내용이 궁금하시면 아래 글을 참고해 보기 바랍니다
http://mkexdev.net/Article/Content.aspx?parentCategoryID=1&categoryID=5&ID=515
그럼 이것으로 강좌를 마치도록 하겠습니다
좋은 하루 되세요~~
'SW개발' 카테고리의 다른 글
[C# 기초강좌] 14. C# 연산자 오버로딩 (0) | 2023.11.20 |
---|---|
[C# 기초강좌] 12. C# 인덱서와 프로퍼티 (0) | 2023.11.06 |
[C# 기초강좌] 11. C# 오버로딩과 오버라이딩 그리고 new (2) | 2023.11.02 |
[C# 기초강좌] 10. C# 생성자와 소멸자 (0) | 2023.11.02 |
[C# 기초강좌] 9. C# 배열 (0) | 2023.11.02 |