* const

 

C++와 다르게 C# const(constant의 줄임말이다.) 키워드는 변수의 생명 주기동안 변경되지 않는 상수 변수를 위해 사용된다. 선언과 동시에 상수 변수에 값을 할당해야 한다. 선언 시 값이 상수 변수에 할당되고 난 이후에는 변경되지 않는다.

 

상수 변수의 값은 컴파일 타임 값, const 키워드를 통해 선언되는 변수는 컴파일 타임 상수이다.

// C++ constexpr과 비슷한 것 같다.

 

상수 객체를 선언하기 위해 const 키워드를 사용할 수 없다. const 키워드는 기본 데이터 타입(int, float, string 등)에서만 사용 가능하다.

const string constString = "test text";

// 기본 데이터 타입에 대해서만 const사용할 수 있다.
// 클래스 객체는 불가능하다.
// const MyClass constMyClass1 = new MyClass();
            
// null을 할당하는 것은 가능하다.
const MyClass? constMyClass2 = null;

오른쪽에는 상수가 와야하는데 new MyClass()는 상수가 아니기 때문에 불가능하다.

 

 

* readonly

 

- 정의

 

readonly 키워드는 읽기전용 변수를 정의하기 위해 사용된다. 변수는 클래스 스코프나 생성자에서만 값을 할당 받을 수 있다. 다른 곳에서는 readonly 변수의 값을 변경할 수 없다.

 

readonly는 크게 다음과 같은 위치에서 사용될 수 있다.

  1. readonly (필드) : 해당 필드가 선언이나 생성자에서만 할당될 수 있음을 나타낸다.
  2. readonly struct : struct가 불변임을 나타낸다. readonly struct의 모든 필드는 readonly여야 한다.
  3. readonly (struct의 필드) : 해당 struct 필드가 struct의 상태를 변경하지 않음을 나타낸다.
  4. ref readonly (메소드) : 참조 값을 반환하지만 수정할 수 없다.

 

 

- 동작 방식

 

readonly 필드는 생성자 이후에 할당될 수 없다. 값 타입, 참조 타입 마찬가지로 필드의 값은 변경할 수 없다.

class ReadonlyTestClass1
{
    public ReadonlyTestClass1(int input)
    {
        Value = input;
    }

    public readonly int Value;
}

class ReadonlyTestClass2
{
    public ReadonlyTestClass2(string input)
    {
        Value = input;
    }

    public readonly string Value;
}

static void Main(string[] args)
{
    /* 값 타입 */
    ReadonlyTestClass1 readonlyTestClass1 = new ReadonlyTestClass1(3);

    // 변경할 수 없다.
    //readonlyTestClass1.Value = 3;

    //==================================================================================================

    /* 불변 참조 타입 */
    ReadonlyTestClass2 readonlyTestClass2 = new ReadonlyTestClass2("text");
    string tempString = new string("text1");

    // 새로운 문자열을 가리키도록 변경할 수 없다.
    //readonlyTestClass2.Value = tempString;
}

 

ref readonly를 반환하는 경우 다음과 같이 작성하면 된다.

class ReturnRefReadonly
{
    public ref int getValue() { return ref value; }
    public ref readonly int getValueReadonly() { return ref value; }
    private int value = 3;
}

static void Main(string[] args)
{
    ReturnRefReadonly rrr = new ReturnRefReadonly();
    ref int temp1 = ref rrr.getValue();
            
    ref readonly int temp2 = ref rrr.getValueReadonly();
    //temp2 = 5; // readonly라서 불가능하다.
}

 

 

- 주의 사항

 

@ 데이터를 참조하는 값(힙의 메모리 주소 값이라고 생각해도 될 것 같다.)과 실제 데이터의 값

 

위에서 readonly 필드의 값이 변경되지 않음을 알 수 있었다. 하지만 참조 타입의 경우 readonly 필드의 값은 특정 메모리를 가리키는 주소 값 같은 것이기 때문에 문제가 생길 수 있다. 실제 데이터 관점에서 하나씩 따져보면 다음과 같다.

  1. 값 타입 : readonly 변수가 데이터를 직접 포함하고 있기 때문에 데이터를 변경할 수 없다. 데이터가 보호된다.
  2. 불변 참조 타입 :  readonly 변수이기 때문에 항상 동일한 불변 데이터를 참조해야 한다. 불변 데이터이기 때문에 데이터가 보호된다.
  3. 가변 참조 타입 :  readonly 변수이기 때문에 항상 동일한 데이터를 참조해야 한다. 하지만 데이터는 변경될 수 있다.  

 

1, 2번은 데이터가 보호된다. 위에서 확인했다.

하지만 3번 가변 참조 타입일 때는 가리키는 데이터만 변경되지 않을 뿐 실제 내용은 변경될 수 있다.

class ReadonlyTestClass3
{
    public ReadonlyTestClass3(StringBuilder input)
    {
        Value = input;
    }

    public readonly StringBuilder Value;
}

static void Main(string[] args)
{
    /* 참조 타입 */
    ReadonlyTestClass3 readonlyTestClass3 = new ReadonlyTestClass3(new StringBuilder("text"));
    StringBuilder tempStringBuilder = new StringBuilder("text1");

    // 새로운 문자열을 기리키도록 변경할 수 없다.
    //readonlyTestClass3.Value = tempStringBuilder;

    // 하지만 readonly가 적용되는 것은 가리키고 있는 참조 정보이다. 참조 정보만 건드리지 않으면 원본을 수정해도 문제없다.
    tempStringBuilder.Append("text1"); // 컴파일 가능
}

위의 코드에서 필드 Value는 StringBuilder가 할당된 힙 공간을 가리키고 있는 참조 값이다. 해당 힙 공간을 가리키는 사실만 변경하지 않는다면 실제로 데이터가 들어있는 StrinbBuilder의 내용을 바꿔도 상관없다. 이러한 보안 취약점 때문에 MS에서는 가능한 readonly 가변 참조 타입으로 선언하지 말라고 되어있다.

 

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/readonly

 

readonly keyword - C# Reference

Table of contents readonly (C# Reference) Article 06/18/2022 3 minutes to read 14 contributors In this article --> The readonly keyword is a modifier that can be used in four contexts: In a field declaration, readonly indicates that assignment to the field

docs.microsoft.com

 

 

@ C++에서의 해결 방법

 

참고로 C++에서는 이러한 문제점을 막기 위해 가리키는 대상의 불변과 대상의 데이터 불변을 const 키워드를 사용하여 따로 설정할 수 있도록 되어있다. 

string stringValue1("text1");
string stringValue2("text2");

//===============================================================
/* const DataType* 형식 */

const string* constStringPtr = &stringValue1;
	
// 가리키고 있는 대상의 데이터를 수정할 수 없다.
//constStringPtr[0] = 'a';

// 가리키는 대상을 수정할 수 있다.
constStringPtr = &stringValue2;


//===============================================================
/* DataType* const 형식 */
	
string* const stringPtrConst = &stringValue1;
	
// 가리키고 있는 대상의 데이터를 수정할 수 있다.
stringPtrConst[0] = 'a';

// 가리키는 대상을 수정할 수 없다.
//stringPtrConst = &stringValue2;


//===============================================================
/* const DataType* const 형식 */

const string* const constStringConstPtr = &stringValue1;
// 가리키고 있는 대상의 데이터를 수정할 수 없다.
//constStringConstPtr[0] = 'a';

// 가리키는 대상을 수정할 수 없다.
//constStringConstPtr = &stringValue2;

 

 

@ C#에서의 대안

 

C#에서는 C++에서 원본 데이터의 수정을 막아주는 const와 같은 도구가 없다. 이 문제를 해결하기 위해서는 다음과 같은 방법을 생각해볼 수 있다.

 

  • 참조 객체를 반환하는데 원본 객체를 변경 시키지 않으려면 원본을 반환하지 말고 깊은 복사를 통해 새로운 객체를 생성하여 반환한다. 문제점은 단순히 객체를 읽었을 뿐인데 힙 메모리에 새로운 객체가 할당되고 가비지 컬렉터는 이 쓰레기를 처리하기 위해 고생한다는 점이다.
  • 참조 객체를 직접 반환하는 메소드 대신 객체에서 원하는 값만 복사하여 반환하는 메소드를 제공한다.

 

 

* const vs readonly

 

const는 컴파일 타임 상수를 정의할 때 사용한다. 필드의 선언에서만 초기화할 수 있다. 생성자에서 수정할 수 없다.

 

readonly는 필드의 선언과 생성자에서 여러 번 할당 가능하다. 생성자에 따라서 다른 값을 가질 수 있다. 런타임 상수로 사용할 수 있다.

class Const_ReadOnly
{
    public Const_ReadOnly(int value)
    {
        // 변경할 수 없다.
        //ConstValue = value;

        // 생성자에서 얼마든지 변경가능하다.
        ReadonlyValue = value;
        if(0 == value)
        {
            ReadonlyValue = -1;
        }
    }

    public Const_ReadOnly(int value1, int value2)
    {
        ReadonlyValue = value1 % value2;
    }

    public const int ConstValue = 5;
    public readonly int ReadonlyValue = 5;
}

 

'C#' 카테고리의 다른 글

[C#] abstract  (0) 2022.06.28
[C#] abstract class vs interface  (0) 2022.06.28
[C#] ref, out  (0) 2022.06.28
[C#] 강력한 형식 언어(Strongly Typed Language)  (0) 2022.06.28
[C#] var  (0) 2022.06.28

+ Recent posts