멈추지 않고 끈질기게
[C#] C# 9.0 기능(record 타입, 초기화 전용 속성(init), 타겟 타이핑(new(), 조건 연산자)) 본문
※ 해당 포스팅은 개인의 공부 정리용 글입니다. 틀린 내용이 있다면 추후 수정될 수 있습니다.
※ 해당 포스팅은 .Net 7.0 버전을 기준으로 작성되었습니다.
※ 해당 포스팅은 하기 출처들을 참조하였습니다.
- https://learn.microsoft.com/ko-kr/dotnet/csharp/whats-new/csharp-9#init-only-setters
- https://www.csharpstudy.com/latest/CS9-record.aspx
이번 포스팅에서는 C# 9.0의 신규 기능 record, init 등에 대해 알아보겠습니다.
1. record 형식
record는 C# 9.0에서 추가된 형식 키워드로, class나 struct 앞에 붙여 레코드 타입의 class, struct를 정의할 수 있습니다(record 뒤를 생략할 경우 클래스로 정의). record 클래스의 경우 일반적인 클래스와 마찬가지로 상속 등의 기능을 사용할 수 있습니다.
// record class 선언
public record Monster
{
public int id;
public string name;
public int hp;
public int atk;
public int def;
public Monster(int id, string name, int hp, int atk, int def)
{
this.id = id;
this.name = name;
this.hp = hp;
this.atk = atk;
this.def = def;
}
}
class Program
{
static void Main(string[] args)
{
Monster monster1 = new Monster(1001, "달팽이", 100, 10, 5);
Monster monster2 = new Monster(1001, "달팽이", 100, 10, 5);
Console.WriteLine($"두 몬스터는 같은가? : {monster1.Equals(monster2)}");
Console.WriteLine($"두 몬스터의 주소는 같은가? : {ReferenceEquals(monster1, monster2)}");
}
}
위 코드는 record class 정의 및 사용 예시입니다. 코드를 보면 선언 방식이나 사용법은 일반적인 클래스와 크게 다르지 않다는 점을 알 수 있습니다.
다만 일반적인 클래스와 record 클래스의 가장 큰 차이점은 객체의 값을 기준으로 동일 여부를 판단한다는 점입니다. 메인 함수를 보면 모든 변수의 값을 동일하게 선언한 Monster 객체를 두 개 생성하고, Equals() 함수로 동일 여부를 판단하고 있습니다. 사진 1을 보면 Equals() 함수가 true를 반환, 즉 동일 객체로 취급함을 알 수 있습니다. ReferenceEquals()로 동일 주소를 참조하고 있는지 여부를 확인한 결과만 False를 반환하고 있습니다.
상기 코드에서 Monster 클래스를 record 타입이 아닌 일반 클래스로 정의한 뒤 실행해보면, 사진 2와 같이 다른 객체로 취급하는 것을 확인할 수 있습니다.
그림 1은 일반 class와 record class의 차이점을 도식화한 것입니다. monster1, monster2은 각각 new 키워드로 heap 영역에 메모리를 할당하여 생성한 객체를 가리키므로, 참조 기준으로 동일 여부를 판단하는 일반적인 class의 경우 다른 객체로 취급합니다. 하지만 record class는 값을 기준으로 동일 여부를 판단하므로, 가리키는 객체가 가진 값들이 모두 동일하다면 동일 객체로 취급합니다. 이는 record class의 경우 컴파일러가 Equals() 함수를 오버라이딩하기 때문입니다.
참고로 record class는 with 키워드를 사용해 record class 객체의 일부 값만 변경한 객체를 쉽게 만들 수 있습니다. 다음은 with 키워드를 사용한 코드 예시입니다.
public record Monster
{
// 상기 코드의 Monster와 동일한 부분 생략
// ToString() 재정의
public override string ToString()
{
return $"Id: {id}, name: {name}, hp: {hp}, atk: {atk}, def: {def}";
}
}
class Program
{
static void Main(string[] args)
{
Monster monster1 = new Monster(1001, "달팽이", 100, 10, 5);
Monster monster2 = monster1 with { id = 1002, name = "버섯" };
Console.WriteLine(monster1.ToString());
Console.WriteLine(monster2.ToString());
}
}
2. 초기화 전용 속성(init)
C# 9.0에서 추가된 초기화 전용 setter(init only setter)를 통해 속성을 초기화 전용으로 선언할 수 있습니다. 속성을 정의할 때 set 대신 해당 키워드를 사용하게 되면, 초기화 이후 readonly 키워드를 붙인 변수와 마찬가지로 값의 수정이 불가능하게 됩니다.
public struct InitTest
{
// 초기화 전용 속성
public int InitialNum { get; init; }
}
class Program
{
static void Main(string[] args)
{
InitTest test = new InitTest { InitialNum = 100 };
// 컴파일 에러 발생
test.InitialNum++;
}
}
차이점이라면 초기화 전용 속성은 초기화를 외부에서 실행할 수 있지만, 읽기 전용 속성의 경우 생성자에서만 초기화 할 수 있습니다. 다음 예시는 읽기 전용 속성을 외부에서 초기화 시도 시 모습입니다.
public struct InitTest
{
// 읽기 전용 속성
public readonly int InitialNum { get; }
}
class Program
{
static void Main(string[] args)
{
// 컴파일 에러
InitTest test = new InitTest { InitialNum = 100 };
}
}
3. Target Typing
타겟 타이핑(Target Typing)이란 어떤 객체의 타입을 문맥을 통해 추론하는 기능을 말합니다. 가장 대표적인 예시가 var 키워드로, 'var (변수이름)'과 같은 식으로 선언하면 초기화 객체에 따라 해당 변수의 타입을 정해주는 것도 타겟 타이핑에 해당합니다.
C# 9.0에서는 new 키워드를 통한 객체 생성에 타겟 타이핑을 지원하게 바뀌었습니다. 이제 new를 통해 객체 생성 시, 참조할 변수와 동일한 타입이라면 객체 타입을 생략 가능합니다.
static void Main(string[] args)
{
Monster monster1 = new Monster();
Monster monster2 = new();
}
이 기능은 원래 알고는 있었는데, 저는 monster1과 같이 타입을 전부 작성하는 쪽을 선호합니다. 사실 var 키워드도 자주 사용하지 않는 편이긴 합니다. 다소 귀찮아도 타입을 명시적으로 작성해주는 편이 중간에 헷갈리지 않고, 다른 사람이 코드를 읽을때도 타입을 헷갈리지 않을 거라 생각해서입니다. 다만 타입 이름이 길어서 타이핑하기 불편한 경우도 있고, 코드가 간결해지는 것은 사실이므로 경우에 따라 사용할 수 있는 것이 좋겠습니다.
또한 C# 9.0부터 같은 클래스를 상속받는 타입이라면 부모 타입 변수로 자식 타입의 객체를 참조하는 식을 삼항 연산자를 통해 작성할 수 있습니다. 다음은 삼항 연산자를 사용해 부모 타입 변수로 참조하는 코드 예시입니다.
public class BaseClass { }
public class Monster : BaseClass { }
public class Player : BaseClass { }
class Program
{
static void Main(string[] args)
{
Monster monster = new();
Player player = new();
// 부모 타입 변수로 자식 타입 참조
BaseClass bc = monster.Equals(player) ? monster : player;
}
}
Player와 Monster는 모두 BaseClass를 상속받으므로, BaseClass 타입의 변수로 참조하는 식을 삼항 연산자를 통해 작성할 수 있습니다. 위 예시에서는 Equals()가 false를 반환하므로 bc는 player를 참조하게 됩니다. 9.0 이전 버전에서 해당 식 사용 시 에러가 발생한다고 합니다.
최근에 유니티 에디터 버전을 2022.3.10f1 으로 업데이트 하면서, 해당 에디터의 C# 버전이 9.0임을 확인하였습니다. 내친김에 9.0 버전의 기능들 중 눈에 띄는 내용을 몇 개 정리해보았는데, record와 같이 처음보는 내용도 있어서 살짝 놀랐습니다. 새로운 버전이 나올때마다 신규 기능들을 체크해보는 습관을 들여야 할 것 같습니다.
'C#' 카테고리의 다른 글
[C#] Thread, ThreadPool, Task (0) | 2023.10.26 |
---|---|
[C#] 열거형(Enum) (0) | 2023.06.28 |
[C#] 형변환 함수 / 연산자 (0) | 2023.06.01 |
[C#] StringBuilder (0) | 2023.02.20 |
[C#] 가비지 컬렉터(Garbage Collector) (0) | 2023.02.20 |