[C#] 17. 구조체(struct) 그리고 값 형식 참조(Value type reference)와 참조(포인터) 형식 참조(Reference type reference)


Study/C#  2020. 7. 23. 11:22

안녕하세요. 명월입니다.


이 글은 C#에서의 구조체(struct) 그리고 값 형식 참조(Value type reference)와 참조(포인터) 형식 참조(Reference type reference)에 대한 글입니다.


이전 글에서 제가 클래스와 인스턴스 생성에 대해서 설명한 적이 있습니다.

링크 - [C#] 09. 클래스 생성하는 방법(생성자, 소멸자)

링크 - [C#] 10. 인스턴스 생성(new)과 메모리 할당(Stack 메모리와 Heap 메모리) 그리고 null


기본적으로 C#에서 구조체라는 것은 형태가 구조는 클래스와 같습니다.

using System;
namespace Example
{
  // 구조체 생성
  struct Example
  {
    // 맴버 변수
    private int data;
    // 맴버 변수 설정 함수
    public void SetData(int data)
    {
      // 맴버 변수에 값 설정
      this.data = data;
    }
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("data - " + this.data);
    }
  }
  class Program
  {
    // 실행 함수
    public static void Main(string[] args)
    {
      // 인스턴스 생성
      Example ex = new Example();
      // 값 설정
      ex.SetData(10);
      // 함수 실행
      ex.Print();

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

그럼 무엇이 다른가 했을 때, 결정적으로 구조체는 값 형식 참조를 하고 클래스는 참조 형식 참조를 하는 차이입니다.

즉, 클래스의 경우는 Heap에 클래스의 인스턴스가 생성이 되고 Stack에 변수로 주소값을 설정합니다.

그럴 경우 Stack에 다른 변수로 이꼴(=)를 사용해서 값을 설정하거나 파라미터로 클래스를 넘겨서 설정을 바꾸면 당연히 원 Stack 변수에도 영향이 갑니다.

using System;
namespace Example
{
  // 클래스 생성
  class Example
  {
    // 맴버 변수
    private int data;
    // 맴버 변수 설정 함수
    public void SetData(int data)
    {
      // 맴버 변수에 값 설정
      this.data = data;
    }
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("data - " + this.data);
    }
  }
  class Program
  {
    // 실행 함수
    public static void Main(string[] args)
    {
      // 인스턴스 생성
      Example ex = new Example();
      // 값 설정
      ex.SetData(10);
      // ex 인스턴스를 ex1에 대입
      Example ex1 = ex;
      // ex1의 함수로 맴버 변수 값 설정
      ex1.SetData(20);
      // ex를 출력하면 값이 변경되어 있다.
      ex.Print();

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

값을 보면 ex 변수의 data값이 변경되어 있습니다. 즉, 아래와 같은 형태로 되어 있습니다.

이걸 예전에 설명했습니다.


구조체는 다릅니다.

using System;
namespace Example
{
  // 구조체 생성
  struct Example
  {
    // 맴버 변수
    private int data;
    // 맴버 변수 설정 함수
    public void SetData(int data)
    {
      // 맴버 변수에 값 설정
      this.data = data;
    }
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("data - " + this.data);
    }
  }
  class Program
  {
    // 실행 함수
    public static void Main(string[] args)
    {
      // 인스턴스 생성
      Example ex = new Example();
      // 값 설정
      ex.SetData(10);
      // ex 인스턴스를 ex1에 대입
      Example ex1 = ex;
      // ex1의 함수로 맴버 변수 값 설정
      ex1.SetData(20);
      // ex를 출력하면 값이 변경되어 있지 않다.
      ex.Print();

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

위의 소스에서 Example 클래스(class)에서 구조체(struct)로 바꾼 것 밖에 없습니다만 결과는 다릅니다.

위처럼 생성이 되어있습니다. 이처럼 구조체는 이꼴(=)만으로 구조체가 복제가 되었습니다. 이것을 C#에서는 값 형식 찹조(Value type reference)라고 하고 위 클래스처럼 포인터만 복사가 되는 것을 참조 형식 참조(Reference type reference)라고 합니다.

또, 우리가 원시 데이터를 사용할 때, int a = 1; int b = a; 라고 했을 때 b의 값을 변경해도 a의 값이 변하지 않습니다. 이것도 값 형식 참조입니다.


그럼 이 구조체(struct)를 함수에서 넘길 때 값 형식 참조가 아닌 참조 형식 참조로 넘기고 싶을 때가 있습니다. 그럴 때는 ref 키워드를 사용해서 참조형식으로 바꿀 수 있습니다.

using System;
namespace Example
{
  // 구조체 생성
  struct Example
  {
    // 맴버 변수
    private int data;
    // 맴버 변수 설정 함수
    public void SetData(int data)
    {
      // 맴버 변수에 값 설정
      this.data = data;
    }
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("data - " + this.data);
    }
  }
  class Program
  {
    // ref를 사용해서 파라미터에서 값 형식 참조가 아닌 참조형식 참조로 변경이 가능하다.
    static void SetExampleData(ref Example p1)
    {
      p1.SetData(20);
    }
    // 실행 함수
    public static void Main(string[] args)
    {
      // 인스턴스 생성
      Example ex = new Example();
      // 값 설정
      ex.SetData(10);
      SetExampleData(ref ex);
      // ex를 출력하면 값이 변경되어 있다.
      ex.Print();

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

당연한 이야기이지만 ref를 빼면, 구조체는 파라미터에서 값 형식 참조로 넘기기 때문에 ex의 값이 변경되어 있지 않다.

클래스의 경우는 참조 형식 참조이기 때문에 필요없는 키워드입니다.


구조체는 클래스와 다르게 몇가지 특성이 있습니다.

먼저 파라미터가 없는 생성자는 만들 수 없습니다.

맴버 변수를 public으로 외부에서 초기 값을 설정한다면 new 키워드를 사용할 필요가 없습니다.

using System;
namespace Example
{
  // 구조체 생성
  struct Example
  {
    // 맴버 변수
    public int data;
    // 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("data - " + this.data);
    }
  }
  class Program
  {
    // 실행 함수
    public static void Main(string[] args)
    {
      // new 키워드가 없음
      Example ex;
      // 맴버 변수 설정
      ex.data = 10;
      // ex를 출력하면 값이 변경되어 있다.
      ex.Print();

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

그러므로 구조체는 null이 존재하지 않습니다.

마지막으로 구조체는 기본구조가 값 형식 참조이기 떄문에 상속이라는 게 존재하지 않습니다.


여기까지 구조체에 대한 설명이었습니다만, 이 구조체라는게 제 생각에는 C++의 흔적(?)이지 않을까 싶습니다. C++에서는 구조체를 사용하면 바이너리를 구조체 형식으로 그대로 읽을 수 있기 때문인데...

C#에서는 그게 되지 않습니다. 그러므로 사실 구조체는 실무에서 전혀 사용하지 않습니다.

왜나햐면 클래스와 사용 방법도 비슷하고 오히려 캡슐화와 클래스의 참조 형식 참조과 값 형식 참조의 차이가 있어서 혼란만 주기 때문에 가독성을 많이 떨어트리는 단점이 있습니다...


여기까지 C#에서의 구조체(struct) 그리고 값 형식 참조(Value type reference)와 참조(포인터) 형식 참조(Reference type reference)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.