[C#] 공식문서 학습하기 5 : ~형식시스템 / 레코드

Sean·2024년 9월 20일
0

C# 학습

목록 보기
5/5

참고 자료: MS Document

  • C#이 생각보다 자료가 많이 없어서 그냥 공식문서 보고 정리하려고 한다.
    일단은 공식문서상의 구조를 따라가려고 하는데 내용은 내 맘대로 써질 예정
  • 본질이 iOS 개발자라 많이 비교하면서 학습을 진행할 예정이다.
    - @>--- 이런 기호와 함께 '기울임', '작게' 표시 되면 개인적인 생각이다.
  • 당연 이건 다른 언어를 했던 사람들이 C# 공부시 그나마 조금 편하라고 만들어 보는거지 아예 코드 짜는 사람이 처음이라면 이해 안될 수 있는다.
  • 이해 안되는건 뒤에 나올 내용이 앞에 나와서 그러는거니까 첨보는 단어면 일단 넘어가면 뒤에 다시 나온다.

형식 시스템 / 레코드

@>--- 일단 레코드라는건 swift에서 들어보지 못한 타입이다. 그러므로 내용을 보며 비슷한걸 찾아보겠다.

  • 레코드는 데이터 모델 작업에 특별한구문과 동작을 제공하는 클래스 또는 구조체 이다.
  • record 한정자는 주 역할이 데이터를 저장하는 형식에 유용한 멤버를 합성하게 한다.

레코드 사용하는 경우

  • 권고사항 (다음의 상황에서는 클래스나 구조체보다 레코드 사용이 좋다.)
    1] 값 같음 여부에 따라 달라지는 데이터 모델을 정의하려는 경우
    2] 변경할 수 없는 개체의 형식을 정의하려고 할때

값 같음?

  • 레코드의 경우에 값 같음형식이 일치하고 모든속성 및 필드 이 동일하다고 비교되는 레코드 형식의 두 변수가 같다는 것을 의미한다.
    • 클래스 같은 참조 타입들의 경우 같음에서 값 같음이 구현되지 않는 한 기본적으로 참조 같음을 의미한다.
      • 즉, 클래스 형식의 두 변수는 같은 개체를 참조하는 경우 같다.
      • 두 레코드 인스턴스의 같음을 확인하는 메서드와 연산자에서 값 같음을 사용한다.
  • 일부 데이터 모델은 값 같음을 사용하지 않는다.
    • 예를 들어 Entity Framework Core(이하 EFC)는 참조 같음을 사용해 개념적으로 하나의 엔티티에 해당하는 엔티티형식의 인스턴스를 하나만 사용하는지 확인한다.
      • 값 같음을 사용하지 않기에 레코드 형식을 사용하기에는 적절치 않다.

불변성

  • 변경할 수 없는 형식은 인스턴스화 된 후 개체의 속성 또는 필드 값을 변경하는 것을 방지하는 형식이다.
  • 불변성은 타입이 스레드로부터 안전해야 하거나 해시 테이블에서 동일하게 남아 있는 해시코드를 사용하는 경우에 유용할 수 있다.
    • 레코드는 변경할 수 없는 형식을 만들고 사용하기 위한 간결한 구문을 제공한다.
  • 불변성은 모든 곳에 다 적합하지 않는다. 위에서 예를 든 EFC는 변경할 수 없는 엔티티 형식을 사용한 업데이트를 지원하지 않는다.

레코드가 클래스 및 구조체와 다른 방식

  • 클래스 또는 구조체 선언하고 인스터스화하는 동일 구문을 레코드와 함께 사용 가능하다.

  • class와 struct 키워드를 recordrecord struct 로 대체해 사용한다.

  • 상속을 표현하기 위한 구문은 레코드 클래스에서 동일하게 지원한다.

  • 레코드가 클래스와 다른점은 다음과 같다.
    1] 기본 생성자에서 위치 매개 변수 사용해 변경할 수 없는 속성을 사용해 타입을 만들고 인스턴스화할 수 있다.

    설명

    이게 무슨말일까?

    • 일단 레코드는 불변 데이터 모델을 쉽게 정의하기 위한 타입이다

    • 클래스나 구조체와 달리 레코드는 기본 생성자를 지원하는데 이 생성자에서 위치 매개 변수를 정의하면, 해당 매개 변수들은 자동으로 읽기 전용속성으로 선언된다.

      • 위치 매개 변수 => 클래스 뒤에 입력하는 매개변수 를 의미 -> 예시 코드에서 FirstName, LastName을 의미
      public record Person(string FirstName, string LastName);
      
      static void Main(string[] args)
      {
          Person gildong = new Person("길동", "홍");
      }
    • 이렇게 main에서 한거처럼 매개 변수를 통해 해당 속성들에 값을 할당하면 이후에는 값을 변경 할 수 없다.

      근데 이게 왜??

      • 일단 클래스는 기본적으로 이런 방식의 간결한 정의가 불가능!
      • 불변 속성을 가진 클래스를 만들려면 조금 코드가 많이 길어짐..
        public class Animal
        {
            public string Name { get; }
            public Animal(string name)
            {
                this.Name = name;
            }
        }
      • 코드가 길어지는건 물론이고, 불변성 유지 위한 추가 작업이 필요

2] 클래스에서 참조 같음 또는 같지 않음을 나타내는 동일한 메서드와 연산자가 레코드에서 같음 또는 같지 않음을 나타낸다.

  • 예: == 및 Object.Eauals(Object)

3] with 식을 사용해 선택된 속성에 새 값을 포함하는 변경할 수 없는 개체의 복사본을 만들 수 있다.

설명

with 식?

  • 레코드와 함꼐 도입되었으므로 레코드와 함꼐 보는게 맞음

  • 기존 객체를 기반으로 일부 속성만 변경해 새로운 객체를 생성시에 사용

  • with 식은 기존 객체의 값을 복사해 새 객체를 생성하면서, 특성 속성의 값만 변경 할 수 있도록 해주는 구문

    • 이를 통해 비파괴적 복사가 가능

      비파괴적 복사??

      • 원본 객체를 변경하지 않고, 그 객체를 기반으로 새 객체를 생성하는것을 의미
        • 얕은 복사랑 다른게 없군
        • 필요한 변경 사항만 반영된 새 객체를 만들다 보니 불변 객체를 다루거나 데이터 무결성 유지해야 하는 상황에서 중요
      Person gamdong = gildong with { FirstName = "감동" };
      • 장점
        1. 코드의 간결성
        2. 안정성 : 원본 변경않으므로 사이드 이펙트 방지
        3. 유지보수성 : 상태 변이에 따른 버그 발생 가능성을 줄여줌

      얕은 복사 & 깊은 복사 (shallow copy & deep copy)

      • 얕복: 객체의 필드 값을 그대로 복사, 주소는 동일 주소를 가짐
        • with 가 여기에 해당 : 만약 참조 타입 속성의 내부를 변경하면 원본과 복사본 둘다 영향 받을 수 있음
      • 깊복: 객체의 필드 값, 주소가 가리키는 객체까지 모두 새로복사

      클래스에서도 비파괴적 복사 구현

      • 클래스에서도 가능하게 할 수 있는데 복제메서드와 init 접근자를 사용하면 된다.
      static void Main(string[] args)
      {
          Animal dog = new Animal("Puppy");
          var dog2 = dog.With(p => p.Name = "dog");
      }
      
      public class Animal
      {
          public string Name { get; set; }
          public Animal(string name) => (Name) = (name);
      
          public Animal With(Action<Animal> updater)
          {
              var clone = (Animal)this.MemberwiseClone();
              updater(clone);
              return clone;
          }
      }
      • 이런식으로 하면 얼추 비슷한 동작을 하는걸 클래스에서도 만들 수 있다.

      복제 메서드?

      • 현재 객테의 상태를 복사해 새 객체를 생성하는 메서드
      • 이를 통해 원복 객체를 변경치 않고 일부 속성만 수정된 새로운 객체를 생성 가능함. (위에서 이야기 하던 with 랑 비슷하네)

      목적: with식이랑 흡사한 목적

      • 비파괴적 복사(얕복)
      • 객체 복사
      • 변형된 객체 생성

      방법: 클래스에서도 비파괴적 복사 구현의 코드 참고

      • [자기 클래스] [메서드명] (매개변수)로 구현
      • MemberwiseClone() 이라는 .NET의 제공 메서드를 사용
        • 현 객체의 얕복본을 생성
      • 내부에 다른 값들을 넣고 다른 방식으로 하고 싶으면 해당 메서드를 알아서 잘 구현 하면 된다.

4] 레코드의 ToString 메서드는 개체 형식 이름과 모든 퍼블릭 속성의 이름과 값을 표시하는 형식 문자열을 만든다

5] 레코드는 다른 레코드에서 상속될 수 있다.

  • 레코드 < - > 클래스 : 서로가 서로를 상속할 수는 없다

  • 레코드 구조체는 컴파일러같음(==)ToString을 위한 메서드를 합성한다는 점에서 구조체와 다르다.

    • 값 기반의 동등성을 지원하며 불면 데이터 모델 정의에 유용하다.
  • 레코드 구조체 와 구조체의 차이점
    1] 동등성 비교

    • 구조체
      • 기본적으로 구조체는 값 타입이기에 모든필드의 값을 비교해 동등성을 판단
      • 그러나 Equals(), GetHashCode() 메서드 및 ==, != 연산자를 자동으로 오버라이드 하지 않기에 필요에 따라 직접 오버라이드해서 동등성 비교를 구현해야 한다.
    • 레코드 구조체
      • 컴파일러가 자동으로 메서드나 연산자를 생성한다.
      • 모든 필드의 값을 기반으로 동등성을 비교하도록 구현된다.
        • 즉 별도의 구현 없이 값 기반 동등비교가 가능

    2] ToString 메서드

    • 구조체

      • 기본적으로 해당 메서드는 형식의 전체 이름을 반환한다.
      • 필드의 값을 포함한 문자열을 얻으려면 해당 메서드를 오버라이드 해야 한다.
    • 레코드 구조체

      • 컴파일러가 자동으로 해당 메서드를 생성해 형식 이름모든 퍼블릭필드 또는 속성의 이름을 포함한 문자열을 반환한다.
      public struct Point
      {
          public int X { get; }
          public int Y { get; }
          public Point(int x, int y) => (X, Y) = (x, y);
      }
      
      public record struct PointXY(int X, int Y);
      
      static void Main(string[] args) {
          var p1 = new Point(1, 2);
          var p3 = new PointXY(3, 4);
          Console.WriteLine(p1.ToString());
          Console.WriteLine(p3.ToString());
      }
      
      // 결과 ----
      // LearnMSDoc.Point
      // PointXY { X = 3, Y = 4 }

    컴파일러가 메서드를 합성한다?

    • 컴파일러가 해당 메서드의 코드를 자동으로 생성해준다는 의미로 개발자가 직접 코드를 짤 필요가 없다는 소리이다.
    • 코드의 간결성, 일관성 있는동작, 불변 데이터 모델링에 적합한 동작을 만들어준다.
  • 컴파일러는 위치 레코드 구조체에 대한 Deconstruct메서드를 합성한다.

    위치 레코드 구조?

    • record struct 를 말하는거 같음
    • C# 에서 구조체와 레코드의 특성을 결합한 타입으로 타입이며 불변성값 기반 동등성을 지원한다.
    • 매개변수를 사용해 정의되며 컴파일러가 여러 유용한 메서드와 속성을 자동으로 생성해준다.

    Deconstruct 메서드?
    @>--- record sturct 선언하며 매개변수만 넣을 때 그게 잘 붙는지 그거 해주는거 같음

    • 객체의 필드를 개별 변수로 분해할 수 있게 해주는 메서드이다.
    • 이 메서드를 사용하면 튜플 분해 구문을 통해 객체의 속성 값을 손쉽게 추출할 수 있다.
    public void Deconstruct(out int X, out int Y)
    {
        X = this.X;
        Y = this.Y;
    }
    • 일반 구조체에서는 해당 메서드가 자동으로 생성되지 않기에 수동으로 구현해야 한다.
    • 위치 레코드 구조체의 장점으로는 자동 생성된 멤버, 불변성 지원, 코드 간결성 이 있다.

  • 컴파일러는 record class의 각 기본 생성자 매개 변수에 대한 공개 초기화 전용 속성을 합성한다.
    • 레코드 클래스에서 기본 생성자를 정의하면, 컴파일러는 각 매개 변수에 대응하는 공개 init 전용 속성을 자동으로 생성한다.

    • 읽기 전용이며 객체 init또는 with식을 통해서만 값을 설정할 수 있다.

      public record class Person(string FirstName, string LastName);
      
      public string FirstName { get; init; }
      public string LastName { get; init; }
  • record struct 에서 컴파일러는 공개 읽기/쓰기 속성을 합성한다.
    • 기본 생성자(위치 매개 변수)를 정의하면, 컴파일러는 각 매개 변수에 대응하는 공개 읽기/쓰기 속성을 자동으로 생성한다.

      public record struct Point(int X, int Y);
      
      public int X { get; set; }
      public int Y { get; set; }
  • 컴파일러는 record 한정자를 포함하지 않는 classstruct 형식에서 기본 생성자 매개 변수에 대한 속성을 만들지 않는다.
    • 레코드 한정자가 없는 일반에서는 기본 생성자(위치 매개 변수)를 사용할 수 없다.
    • 따라서 컴파일러는 기본 생성자 매개 변수에 대한 속성을 생성 않는다.
    • 일반 클래스나 구조체에서 생성자 내부에서 수동으로 필드나 속성을 정의하고 초기화해야 한다.
profile
"잘 할 수 있을까?"를 고민하기보단 재밌어 보이는건 일단 하고, 잘하기 위해 그냥 계속합니다.

0개의 댓글