0. 들어가기에 앞서 (삽질의 시작)
C#이나 Visual Studio도 기능 추가를 위해서 업데이트가 진행될 것이다. 작성 시점 C#의 버전은 14까지나 있는 것으로 확인이 되고, 버전이 업그레이드 되면서 많은 기능이 추가되거나 삭제되었을 것이다.
여기에서, C# 9.0에서 추가된 record라는 것을 알아보고자 한다.
...라고 써 볼랬는데 자동완성은 되면서도 안 써지는 모습이다.
그러면 버전 업그레이드를 해야 하나 보다. 버전 업그레이드 할 방법을 구글링 해봤다.
[.net 9.0 SDK 다운로드]
https://dotnet.microsoft.com/ko-kr/download/dotnet/9.0
마이크로소프트의 공식 사이트에 다운 받을 수 있다.
위와 같이 다운 받고 Visual Studio를 들어가보자.
(이때부터 뭔가 쎄함을 느꼈다)
업데이트가 가능하다고는 뜨는데, .net 업그레이드가 안 뜬다. 일단은 업데이트를 해 보았지만 C# 업그레이드는 되지 않았고 왜 안 되는지 여러모로 뒤적여봤지만... 결국 해결 방법을 찾지 못했다.
그 와중에 버전 업그레이드를 하면은 기존 다른 버전으로 작업하던 파일에 손상이 갈 수도 있다고 해서... 이번에는 직접 사용해보지는 않고 공부하는 용도로만 배운 것들을 정리해 놓고자 한다.
Record는 사용자정의 type을 생성할 수 있는 또 다른 방법이다.
클래스 또는 구조체 달리 선언할 수 있는 record 한정자는 주 역할이 데이터를 저장하는 형식에 유용한 멤버를 합성하도록 컴파일러에 지시한다.
record 내에 있는 모든 멤버 변수는 불변의 값을 갖게 되며 클래스처럼 상속 등의 기능을 사용할 수 있다.
선언 방법
// 기본형
// (접근자) record (이름)
ex)
public record Monster
{
public string name;
public int level;
public int attack;
public int hp;
}
이와 같이 클래스를 선언하는 것과 비슷한 양상이지만, 클래스와는 다른 특성을 가지고 있다.
차이가 발생하는 것은 객체가 같은 지를 비교하는 과정에서 발생한다.
이 둘을 비교하기 위한 예시를 아래에 두고자 한다.
아래와 같이 Class와 record의 코드를 짜 보았다고 해 보자.
public class 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;
}
}
internal 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)}");
}
}
Class 에서는 두 몬스터는 같지 않고, 두 몬스터의 주소 또한 같지 않다고 나온다.
반면에 record도 같은 방식으로 작성하면 결과가 다르게 나온다.
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)}");
}
}
이에 대한 결과는 두 몬스터는 같고, 두 몬스터의 주소는 다르다고 나온다.
monster1 과 monster2 자체는 별개로 선언했지만, 두 몬스터가 가진 값 자체는 같기 때문에 같은 것으로 간주하는 것이다.
Class의 경우에 객체를 생성할 때 그 변수를 일일히 입력해야 할 것이다. 하지만 record로 만든 객체를 활용하면 비슷한 객체를 좀 더 간단하게 표현할 수 있다.
//Monser record(상기 참고)
class Program
{
static void Main(string[] args)
{
// id: 1001, 이름: 달팽이, 체력: 100, 공격력: 10, 방어력: 5
Monster monster1 = new Monster(1001, "달팽이", 100, 10, 5);
// id: 1002, 이름: 버섯, 체력: 100, 공격력: 10, 방어력: 5
Monster monster2 = new Monster(id = 1002, name = "버섯");
}
}
C# 9.0 에서 사용할 수 있는 기능으로 init setter가 추가되었다. 이는 setter를 선언할 때 최초에 초기화한 값에서 변경되지 않도록, 불변의 속성을 부여하는 것이다.
public record Monster
{
public string name { get; init; }
public int level { get; init; }
}
class Program
{
static void Main(string[] args)
{
Monster monster = new Monster
{
name = "달팽이",
level = 5
};
}
}
record는 생성자(Constructor)과 Deconstructor을 사용할 수 있다. 이때, 생성자와 Deconstruct를 정의할 필요 없이 아래와 같이 사용하면 멤버 변수를 간단하게 가져올 수 있다.
public record Monster(string name, int level);
// 예제(7)
class Program
{
static void Main(string[] args)
{
Monster dragon = new Monster("드래곤", 50);
// Deconstructor 사용
var (name, level) = dragon;
Console.WriteLine($"{name}, {level}");
}
}
record는 아래와 같이 상속을 사용할 수도 있다.
public record Monster
{
public string name { get; init; }
public int level { get; init; }
}
public record Dragon : Monster
{
public int flySpeed { get; init; }
}
class Program
{
static void Main(string[] args)
{
Monster p1 = new Dragon
{
name = "드래곤",
level = 50,
flySpeed = 30
};
}
}
참고자료
(record MSDN)
https://learn.microsoft.com/ko-kr/dotnet/csharp/fundamentals/types/records
(record)
https://sam0308.tistory.com/85