* 정의

 

dynamic 타입은 컴파일 시간에 타입 검사를 피하기 위해 사용되는 타입이다. 컴파일러는 dynamic 타입을 컴파일 시간에 체크하지 않는 대신 컴파일러는 런타임에 타입을 가져온다. 이러한 dynamic 타입은 dynamic 키워드를 사용해서 정의할 수 있다.

 

dynamic 타입을 통해서 덕 타이핑을 실현시킬 수 있고 이는 어떤 문제를 유연하게 해결할 수 있도록 도와준다. 예를 들어 인터페이스를 설계하여 특정 동작들을 정의하고 이를 사용할 때 그 과정에서 상속 관계를 잘 구성하기 위해서는 경험과 노력이 필요하다. 하지만 덕 타이핑은 상속 관계를 이용하지 않기 때문에 프로그램의 동작 부분만 신경쓰면 된다.

 

하지만 덕 타이핑으로 코드를 작성한다면 비주얼 스튜디오의 기능(리팩토링, 인텔리센스 등)을 사용할 수 없고, 컴파일 시간에 확인할 수 없기 때문에 오류를 조기에 발견하지 못하는 문제점이 있을 수 있을 것 같다.

 

 

* 동작 방식

 

- 컴파일 타임과 런타임에서 처리

 

dynamic 타입으로 객체를 할당했을 때 컴파일러는 객체에 대한 메소드나 프로퍼티의 정확한 이름을 확인하지 못한다. 해당 메소드나 프로퍼티가 존재하지 않더라도 컴파일 타임에는 판단하지 못하기 때문에 이상 없이 컴파일 되지만 런타임에 메소드와 프로퍼티가 호환되지 않으면 예외를 발생시킨다. 매개변수로 잘못넘기거나 잘못변환(암묵적 변환이다.)하는 경우도 마찬가지로 예외를 발생시킨다.

 

하지만 컴파일 타임에서 결정하지 않기 때문에 메소드의 매개변수를 dynamic 타입할 수 있고 이를 통해 런타임에 어떠한 타입도 받을 수 있다.

 

 

- 컴파일 타임에서의 타입과 런타임에서의 타입

 

컴파일러는 dynamic 타입을 object 타입으로 컴파일 하지만  런타임에 실제 타입이 확인된다. dynamic 타입은 런타임에 할당된 값(오른쪽에 있는 값)을 기반으로 타입을 변경된다.

 

var과 다르게 dynamic은 실제 타입을 컴파일 타임에 확인할 수 없다.

 

다음 코드는 런타임에 실제 타입을 확인하기 위해 GetType()을 호출한 코드이다.

dynamic dynamicInt = 100;
Console.WriteLine("Value: {0}, Type: {1}", dynamicInt, dynamicInt.GetType());

dynamic dynamicString = "hello";
Console.WriteLine("Value: {0}, Type: {1}", dynamicString, dynamicString.GetType());
Value: 100, Type: System.Int32
Value: hello, Type: System.String

 

 

- var vs dynamic

 

var dynamic
정적으로 타입이 결정된다. 동적으로 타입이 결정된다.
컴파일 타입에 컴파일러에 의해 결정된다. 런타임에 컴파일러에 의해 결정된다.
컴파일러는 초기화된 값에 따라 타입을 결정하기 때문에 선언 시 초기화 되어야 한다. 컴파일 타임에 타입을 알 수 없기 때문에 선언 시 초기화하지 않아도 된다.
초기화되지 않으면 오류가 발생한다. 초기화되지 않아도 오류가 발생하지 않는다.
인텔리센스 지원 인텔리센스 미지원
초기화시 타입이 결정되고 변경되지 않기 때문에 다른 타입으로 대입 연산을 할 수 없다. 초기화시 타입이 결정되지 않고 타입이 변할 수 있기 때문에 다른 타입의 값으로 대입 연산하는 것이 가능하다. 
함수의 지역변수로만 사용 가능하며 값을 반환하거나 프로퍼티에 사용 불가능하다. 값을 반환하거나 프로퍼티에 사용할 수 있다.

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

[C#] Generic Collection 구현 해보기  (0) 2022.07.01
[C#] 인터페이스(interface)  (0) 2022.06.28
[C#] abstract  (0) 2022.06.28
[C#] abstract class vs interface  (0) 2022.06.28
[C#] const, readonly  (0) 2022.06.28

* List<T> 구현

 

public class MyList<T> : IEnumerable<T>, IEnumerable
{
    private T[] data;
    private int capacity;
    private int count;

    // interface start
    IEnumerator<T> IEnumerable<T>.GetEnumerator() { return new MyList<T>.Enumerator(this, count); }
    IEnumerator IEnumerable.GetEnumerator() { return new MyList<T>.Enumerator(this, count); }
    // interface end

    public int Capacity { get { return capacity; } }
    public int Count { get { return count; } }

    public MyList(int capacity)
    {
        this.data = new T[capacity];
        this.capacity = capacity;
    }

    public T this[int index] 
    { 
        get
        {
            return data[index];
        }

        set
        {
            data[index] = value;
        }
    }

    public void Add(T item)
    {
        if (capacity <= count)
        {
            int newCapacity = capacity * 2;
            Array.Resize<T>(ref data, newCapacity);

            Console.WriteLine($"Resized : [{capacity}] -> [{newCapacity}]");
            capacity = newCapacity;
        }

        data[count++] = item;
    }

    public void Clear()
    {
        Array.Resize<T>(ref data, 0);
        capacity = 0;
        count = 0; 
    }


    public struct Enumerator : IEnumerator<T>, IEnumerator
    {
        private MyList<T> parent;
        private int count;
        private int position;

        // interface start
        T IEnumerator<T>.Current { get { return parent[position]; } }
        object IEnumerator.Current { get { return parent![position]!; } }

        public bool MoveNext()
        {
            ++position;
            if(position >= count)
            {
                return false;
            }

            return true;
        }

        public void Reset() { position = -1; }
        public void Dispose() { Reset(); }
        // interface end

        public Enumerator(MyList<T> parent, int count)
        {
            this.parent = parent;
            this.count = count;
            this.position = -1;
        }
    }
}

 

 

 

 

 

 

 

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

[C#] dynamic  (0) 2022.07.06
[C#] 인터페이스(interface)  (0) 2022.06.28
[C#] abstract  (0) 2022.06.28
[C#] abstract class vs interface  (0) 2022.06.28
[C#] const, readonly  (0) 2022.06.28

* 정의

 

인터페이스는 C#에서 사용되는 추상화하기 위해 사용되는 도구이다. 추상 클래스와 비슷하지만 일반적으로 구현부가 없는 메소드, 이벤트, 인덱서, 프로퍼티만을 가질 수 있고 인터페이스를 상속받은 클래스에서 이 기능들을 구현하도록 되어있다. 접근 제한 한정자를 사용할 수 없으며 모든 것이 public으로 선언된다.

 

// C# 8.0부터 멤버의 구현부가 있을 수도 있고 4가지 이 외에 다른 것들을 멤버로 가질 수 있게 되었다.

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

 

interface - C# Reference

:::no-loc text=interface::: (C# Reference)

docs.microsoft.com

 

 

* 사용 이유

 

  • 기본 구현이 없다면 인터페이스를 상속받은 클래스는 이 기능들을 반드시 구현하도록 강제하기 위해 사용된다. // 기본 구현 기능은 C# 8.0부터 지원하고 있다.
  • 인터페이스는 실제 구현 내용이 없기 때문에 죽음의 다이아몬드 문제가 발생하지 않는다. 덕분에 다중 상속을 할 수 있으며 이는 유연한 개발 환경을 제공한다.
  •  구조체는 ildasm으로 확인했을 때 sealed 클래스인데 구조체가 유일하게 상속받을 수 있는 클래스는 인터페이스이다.
  • 특정한 세부 구현을 숨길 수 있다.

 

 

* 동작 방식

 

- 멤버 선언

 

인터페이스에서 멤버를 선언하면 abstract와 public 키워드가 자동으로 붙게 된다. 따라서 public 접근 제한 한정자를 붙이지 않아도 되는 것이고, abstract 키워드 덕분에 상속받은 클래스에서 구현을 강제할 수 있는 것이다.

 

 

C# 8.0부터는 기본 구현을 가질 수 있으며 기본 구현을 작성하면 abstract 키워드가 붙지 않는다. 따라서 상속받은 클래스에서 반드시 구현하지 않아도 된다.

 

 

- 상속 받은 클래스에서 구현과 이를 다시 상속 받은 클래스

 

상속 받는 클래스에서 구현 시 override 키워드를 따로 붙이지 않아도 된다. 구현된 멤버는 ildasm으로 봤을 때 final 키워드가 붙어 있다. 이 때문에 새로운 클래스에 인터페이스를 구현한 클래스를 상속해도 이미 구현된 인터페이스의 멤버를 오버라이딩할 수 없다. // 그렇다고 컴파일 에러가 발생하는 것은 아니다. 일반적인 함수 선언으로 간주된다.

 

< 호출 코드 >

InterfaceTest test = new InterfaceTest2();
test.DoSomething(); // 오버라이딩 되지 않기 때문에 InterfaceTest의 DoSomething이 호출된다.

 

 

- 여러 인터페이스를 상속받은 경우

 

여러 인터페이스를 상속받을 때 메소드 이름이 같은 경우도 존재한다. 그럴 때는 인터페이스를 명시적으로 구현하면 된다.

다음 코드는 제네릭 컬렉션 List을 직접 구현해 본 코드의 일부이다. GetEnumerator, Current가 상속 받은 인터페이스에 모두 존재하여서 명시적으로 구현하였다.

public class MyList<T> : IEnumerable<T>, IEnumerable
{
    IEnumerator<T> IEnumerable<T>.GetEnumerator() { return new MyList<T>.Enumerator(this, count); }
    IEnumerator IEnumerable.GetEnumerator() { return new MyList<T>.Enumerator(this, count); }

    // ...
    // ...

    public struct Enumerator : IEnumerator<T>, IEnumerator
    {
        T IEnumerator<T>.Current { get { return parent[position]; } }
        object IEnumerator.Current { get { return parent![position]!; } }
        // ...
        // ...
    }
}

 

 

- 특징

 

  • 추상 클래스와 비슷하게 객체를 생성할 수 없다. 
  • 기본 구현을 제외하면 구현부를 가지지 않고 구현부는 상속받은 클래스에서 작성한다.
  • 인터페이스를 상속받은 클래스에서 기본 구현이 없는 모든 멤버를 반드시 오버라이딩하여 구현해야 한다.
  • 클래스의 인스턴스와 관련된 필드를 포함할 수 없다.
  • 생성자를 포함할 수 없다.

 

 

- 개인적인 생각

 

인터페이스는 추상 클래스와 다르게 필드와 같은 인스턴스와 관련된 정보를 담을 수 없다.

 

그렇기 때문에 인터페이스를 상속 받는다는 것은 개인적으로 계층 구조를 표현한다기 보다 동작(기능)들을 정의하기 위해 사용되는 느낌을 받았다.

 

인터페이스는 어떤 객체가 어떠한 동작(기능)을 할 수 있는 지 다중 상속을 통해 유연하게 제공한다고 생각한다. 이러한 유연성 덕분에 Unity의 컴포넌트 기반 설계처럼 전반적인 계층 구조를 신경 쓰지 않고 빠르게 확장해 나갈 수 있는 것 같다.

 

 

* abstract class vs interface

 

https://create-new-worlds.tistory.com/348

 

[C#] abstract class vs interface

* abstract class vs interface abstract class interface 다중 상속 불가능하다. 가능하다. 필드(field) // 인스턴스와 관련있는 것들 포함할 수 있다. 일반적으로 포함할 수 없다. // C# 8.0부터 인스턴스와 관..

create-new-worlds.tistory.com

 

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

[C#] dynamic  (0) 2022.07.06
[C#] Generic Collection 구현 해보기  (0) 2022.07.01
[C#] abstract  (0) 2022.06.28
[C#] abstract class vs interface  (0) 2022.06.28
[C#] const, readonly  (0) 2022.06.28

* 정의

 

데이터 추상화는 특정 세부 내용을 감추고 필요한 부분을 유저에게 보여주는 과정이다. C#에서 추상화 방법이 크게 2가지가 있다.

  • 인터페이스(interface)
  • 추상 클래스(abstract class)

 

인터페이스는 암묵적으로 abstract이고 추상 클래스는 abstract 키워드를 사용하여 명시적으로 정의한다. 이러한 abstract 키워드는 불완전한 구현을 나타내며 클래스, 메소드, 프로퍼티, 인덱서, 이벤트에 사용될 수 있다.

 

 

* 사용 이유

 

  • 추상 클래스는 인터턴스화 될 수 없다. 따라서 다른 클래스의 기본 클래스(base class)로만 사용하고 인스턴스화되지 않도록 하기 위해 추상 클래스를 사용한다.
  • abstract 가 붙은 클래스 멤버는 해당 abstract 클래스를 상속받는 non-abstract 클래스에서 구현되어야 한다. 특정한 기능의 구현을 강제하고 기본적인 것들을 상속하여 사용하기 위해 추상 클래스가 사용될 수 있다.

 

// 추상 클래스를 상속받는 파생 추상 클래스에서 구현해도 인스턴스화 할 수 없기 때문에 의미가 없다.

 

 

* 동작 방식

 

- abstract class

 

클래스 선언 시 abstract 키워드를 붙여서 추상 클래스를 정의할 수 있다.

 

추상 클래스는 인스턴스 생성이 제한된 클래스이다. 추상 클래스를 상속 받는 파생 클래스를 통해서만 추상 클래스에 접근할 수 있다.

 

 

- abstract method, property, indexer, event

 

추상 클래스 내부에서만 abstract 키워드를 붙여서 여러 종류의 클래스 멤버를 선언할 수 있다.

 

추상 클래스 내부에서만 사용할 수 있는 메소드(method), 프로퍼티(property), 인덱서(indexer), 이벤트(event)이다. 구현부가 존재하지 않으며 구현부는 추상 클래스를 상속받은 파생 클래스에서 작성한다.

 

 

- 특징

 

  • 추상 클래스는 인스턴스화될 수 없다.
  • 추상 클래스는 특정 추상 메소드나 액세서(프로퍼티의 get, set)를 포함할 수 있다.
  • 추상 클래스를 선언할 때 sealed 키워드를 같이 작성할 수 없다. abstract는 상속 받아서 사용해야한다는 의미이고 sealed 는 상속을 방지하는 의미를 지닌다. 서로 반대의 의미를 지니기 때문에 같이 사용할 수 없다.
  • 추상 클래스를 상속 받은 non-abstract 클래스는 모든 추상 메소드와 액세서를 구현해야 한다. 
  • 추상 메소드는 암묵적으로 virtual 메소드이다. ildasm에서 보면 나타난다.
  • static, virtual 과 abstract는 같이 사용할 수 없다. abstract는 virtual 키워드가 암묵적으로 붙기 때문에 virtual을 한번 더 쓸 필요 없고, static 멤버는 virtual이 될 수 없다. 메소드나 프로퍼티(프로퍼티도 get, set 메소드이다.)나 마찬가지이다.
  • 파생 클래스에서는 override 키워드를 사용하여 구현부를 정의하면 된다.
  • 메소드나 프로퍼티 선언에서 abstract를 사용하면 구현을 미포함한다는 것을 나타낸다.
  • 추상 메소드는, 프로퍼티, 인덱서, 이벤트는 추상 클래스에서만 허용된다.
  • 추상 메소드 선언은 실제 구현을 제공하지 않기 때문에 구현부가 없다. 메소드 정의는 세미콜론으로 끝난다.

 

 

* abstract class vs interface

 

https://create-new-worlds.tistory.com/348

 

[C#] abstract class vs interface

* abstract class vs interface abstract class interface 다중 상속 불가능하다. 가능하다. 필드(field) // 인스턴스와 관련있는 것들 포함할 수 있다. 일반적으로 포함할 수 없다. // C# 8.0부터 인스턴스와 관..

create-new-worlds.tistory.com

 

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

[C#] Generic Collection 구현 해보기  (0) 2022.07.01
[C#] 인터페이스(interface)  (0) 2022.06.28
[C#] abstract class vs interface  (0) 2022.06.28
[C#] const, readonly  (0) 2022.06.28
[C#] ref, out  (0) 2022.06.28

* abstract class vs interface

 

  abstract class interface
다중 상속 불가능하다. 가능하다.
필드(field)
// 인스턴스와 관련있는 것들
포함할 수 있다. 일반적으로 포함할 수 없다.
// C# 8.0부터 인스턴스와 관련없는 정적 필드와 같은 것들은 포함할 수 있다.
기본 구현(default implementation) abstract가 붙은 멤버는 기본 구현을 지원하지 않는다. 일반적으로 불가능하다.
// C# 8.0 부터 가능하다.
멤버의 abstract 키워드 abstract, non-abstract 멤버를 가질 수 있다. 모두 public abstract이다.
선언 방식 abstract class Name { /* ... */ } interface Name { /* ... */ }
접근 제한 한정자 원하는대로 선언 가능하다. 모두 public으로 선언된다.
개념(concept) extend 로 표현된다. implement 로 표현된다.

 

 

 

 

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

[C#] 인터페이스(interface)  (0) 2022.06.28
[C#] abstract  (0) 2022.06.28
[C#] const, readonly  (0) 2022.06.28
[C#] ref, out  (0) 2022.06.28
[C#] 강력한 형식 언어(Strongly Typed Language)  (0) 2022.06.28

* 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

* ref

 

ref 키워드는 C#에서 참조에 의한 전달로 인수를 전달할 때, 메소드 결과를 참조로 반환할 때 사용된다.

 

 

- 참조에 의한 전달

 

값에 의한 전달이 매개변수가 변수나 상수로부터 값을 복사하는 것과 달리 참조에 의한 전달한 매개변수가 메소드에 넘겨진 원본 변수를 직접 참조한다. 만약 메소드 안에서 매개변수를 수정하면 이 매개변수가 참조하고 있는 원본 변수에 수정이 이루어진다.

static void Swap(ref int a, ref int b)
{
    // 원본에 반영된다.
    int temp = a;
    a = b;
    b = temp;
}

static void Main(string[] args)
{
    int a = 3;
    int b = 2;
    Swap(ref a, ref b);
}

 

 

- 참조로 반환

 

메소드의 반환형으로 ref를 지정할 수도 있는데, 방식에 따라 참조나 값으로 반환 값을 받아서 사용할 수 있다. 참조로 받으면 당연히 원본도 같이 수정된다.

// 지역 변수가 반환될 수 없도록 컴파일러가 잘 잡아낸다.

class MyRefClass
{
    public MyRefClass(int value) { this.value = value; }
    public ref int getValueRef() { return ref value; }

    private int value;
}

static void Main(string[] args)
{
    MyRefClass myRef = new MyRefClass(3);
            
    // 참조를 반환할 때 사용
    ref int valueRef = ref myRef.getValueRef();
    valueRef = 2; // 원본에 반영

    // 값을 반환할 때 사용
    int value = myRef.getValueRef();
}

 

 

* out

 

out 키워드는 C#에서 반환해야할 값이 2개 이상일 때 사용한다. out 키워드를 붙여서 반환 받을 변수들을 메소드에 인수로 전달하고 메소드 내에서 매개변수에 결과를 저장하면 매개변수가 참조하고 있는 원본 변수들이 수정된다. 호출 이후에 결과가 저장된 변수들을 사용하면 된다.

static void MaxMin(int a, int b, out int max, out int min)
{
    // out 키워드 덕분에 max, min은 초기화를 보장받는다.
    if(a < b)
    {
        max = b;
        min = a;
    }
    else
    {
        max = a;
        min = b;
    }
}

static void Main(string[] args)
{
    int max; int min;
    MaxMin(2, 3, out max, out min);
}

 

ref 키워드를 통해서도 똑같이 구현할 수 있지만 out 키워드는 안전 장치가 존재한다. 

 

out 키워드를 사용할 경우 해당 매개변수에 결과를 저장하지 않으면 컴파일 에러가 발생하기 때문에 안정적으로 프로그래밍 할 수 있다. 이러한 강제성 out 키워드를 가진 매개변수는 초기화를 보장받을 수 있다. 따라서 인수로 초기화되지 않은 변수를 넘겨줄 수 있다. (ref는 초기화되지 않은 변수를 넘겨줄 수 없다.)

 

 

* ref vs out

 

ref out
ref로 넘겨주기 전에 변수를 초기화해야 한다. out로 넘겨주기 전에 변수를 초기화하지 않아도 된다.
메소드를 리턴하기 전에 매개변수 값을 초기화할 필요 없다. 메소드를 리턴하기 전에 매개변수 값을 초기화해야 한다.
호출된 메소드가 전달된 매개변수의 값도 변경해야할 때 유용하다. 메소드가 여러 값을 반환할 때 유용하다.
양방향 전달 단방향 전달
참조를 반환할 수 있다.  

 

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

[C#] abstract class vs interface  (0) 2022.06.28
[C#] const, readonly  (0) 2022.06.28
[C#] 강력한 형식 언어(Strongly Typed Language)  (0) 2022.06.28
[C#] var  (0) 2022.06.28
[C#] 박싱과 언박싱(Boxing and Unboxing)  (0) 2022.06.28

* 정의

 

강력한 형식 언어에서는 변수를 사용하기 전에 타입을 명시적으로 선언해야한다. int, char 등 을 포함한 모든 데이터 타입은 미리 프로그래밍 언어에 선언되어 있어야 하고 모든 변수나 상수는 이러한 데이터 타입 중에 하나여야 한다.

 

강력한 형식의 언어는 컴파일러를 통해 데이터 타입과 값이 혼합되어 사용되는 경우를 방지하고 데이터 형식이 준수되도록 한다.

 

C#도 강력한 형식 언어다.

 

 

* 장점

 

  • 엄격한 규칙을 통해 결과의 일관성을 보장한다.
  • 타입 결정에 대한 런타임 패널티가 없다.
  • 오류를 조기(컴파일 타임)에 탐지하여 개발 속도를 올려준다.
  • 컴파일러가 최적화된 코드를 만들어준다.
  • 의도치 않는 형식에 데이터를 읽거나 할당하는 일을 막아줌으로써 실수를 줄여준다. // 예를 들어 정수형 형식에 문자열을 할당하려고 하는 경우 컴파일 에러를 발생시킨다.

 

 

* 단점

 

  • 유연성이 떨어진다. 언어에서 예상치 못한 데이터 타입을 만들어서 사용하는 것을 막고 있다. 
  • heterogeneous collection(혼종, 이기종, 이종 컬렉션)을 정의하기 더 어렵다.

# heterogeneous collection 이란 다른 타입의 데이터를 저장하면서 조회, 업데이트, 순회 등의 기능을 제공하는 데이터 타입이다.

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

[C#] const, readonly  (0) 2022.06.28
[C#] ref, out  (0) 2022.06.28
[C#] var  (0) 2022.06.28
[C#] 박싱과 언박싱(Boxing and Unboxing)  (0) 2022.06.28
[C#] object  (0) 2022.06.28

+ Recent posts