글 작성자: Doublsb

최근 유니티 2021 버전을 주로 사용하게 됨에 따라, C# 9.0의 기능을 정리할 필요성을 느껴 오랜만에 포스팅을 한다.

2022 버전에서도 C# 9.0을 사용하고 있으니 당분간 C# 신기능 포스팅은 이후로는 없을 듯 하다.

 

마이크로소프트 공식 문서를 참조했다.

 


 

레코드

레코드(record) 형식이 새로 추가되었다. 변경할 수 없는 데이터 모델을 지원하기 위해 만들어졌다.

레코드는 초기화 한 뒤에는 객체 내의 멤버들을 변경할 수 없다.

 

public record Person(string FirstName, string LastName);
public record Person
{
    public string FirstName { get; init; } = default!;
    public string LastName { get; init; } = default!;
};

위 코드는 Person이라는 record를 정의하는 방식을 보여준다.

속성의 set 자리에 난데없이 init이 들어가있는 모습을 볼 수 있는데, 이는 레코드의 특성 때문이다.

 

레코드는 최초에 초기화 한 뒤에는 객체의 내용을 변경할 수 없으므로, set 속성 대신 init 속성을 이용해야 한다.

 

  • with 표현식

레코드가 불변이라면 일부만 변경하여 새 객체를 만들고 싶을 때 불편하지 않겠는가?

그럴 때에는 with를 사용하면 된다.

Person person1 = new("Nancy", "Davolio") { PhoneNumbers = new string[1] };
Console.WriteLine(person1);
// output: Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers = System.String[] }

Person person2 = person1 with { FirstName = "John" };
Console.WriteLine(person2);
// output: Person { FirstName = John, LastName = Davolio, PhoneNumbers = System.String[] }
Console.WriteLine(person1 == person2); // output: False

person2 객체를 선언할 때 person1을 토대로 만들되, FirstName만 변경하도록 선언되어 있는 모습이다.

 

  • 값 같음

레코드는 같은 값을 비교해야 할 상황이 있으면 편리하다.

public record Person(string FirstName, string LastName, string[] PhoneNumbers);

public static void Main()
{
    var phoneNumbers = new string[2];
    Person person1 = new("Nancy", "Davolio", phoneNumbers);
    Person person2 = new("Nancy", "Davolio", phoneNumbers);
    Console.WriteLine(person1 == person2); // output: True
}

Person에 Equals 등의 연산자를 재정의하지 않았는데도 완전 일치할 경우 True를 리턴하는 모습이다.

이제 데이터를 위한 클래스를 굳이 만들지 않더라도 데이터 비교가 가능해졌다. 변경은 불가능하지만.

 

  • ToString()을 시전하면
Person { FirstName = Nancy, LastName = Davolio, ChildNames = System.String[] }

레코드 객체에 ToString()을 사용하면 속성 이름과 그 값을 알아서 보여준다.

여기서 ChildNames는 참조 형식인 string[] 타입이라 값 대신 표시되었다.

 

  • 상속

레코드는 레코드에서 상속할 수 있다. 그러나 클래스와는 호환 불가능하다.

 

 

최상위 문

이제는 Main 메서드를 명시적으로 포함하지 않고도 작성이 가능하다... 라고는 하지만 유니티에는 영 쓸모가 없으니 패스한다.

 

 

패턴 일치 개선

패턴 일치가 또 개선됐다.

and와 or에 괄호를 사용하여 우선 순위를 명확하게 쓸 수 있고, 새로운 null 검사 구문을 쓸 수 있다.

public static bool IsLetterOrSeparator(this char c) =>
    c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or '.' or ',';
if (e is not null)
{
    // ...
}

 

 

성능 및 interop

  • 원시 크기 정수

nint, nuint라는 정수 형식이 새로 생겼다. nint는 native int, nuint는 unsigned native int이다.

unmanaged 코드나 저수준 라이브러리에서 사용하므로 일단 패스.

 

  • 함수 포인터

delegate*를 사용하여 함수 포인터를 선언할 수 있다고 한다. C#에서 안전하지 않으므로 패스.

 

  • localsinit 플래그 생략

SkipLocalsInit 속성을 추가하여 해당 객체의 로컬 변수를 0으로 초기화하는 걸 막을 수 있다.

마이크로 최적화급 용도이며, 위험하니 사용하지 않는 것이 맞을 듯. 패스.

 

 

 

new()

이제 만든 개체의 형식을 이미 알고 있으면 형식을 생략할 수 있다.

private List<WeatherObservation> _observations = new();

선언 단계에서 List<WeatherObservation>의 형식을 알고 있으므로, 이제는 굳이 객체 생성 시 써주지 않아도 된다.

 

 

 

무시 항목

이제 이름을 할당할 때 굳이 필요 없다면 작성하지 않아도 된다. 무슨 소리인지 모르겠으면 다음 코드를 보자.

(_, _, area) = city.GetCityInformation(cityName);

세 항목이 필요한 튜플을 작성하는데, 앞의 두 항목은 무시 항목인 _로 서술되어 있다.

이를 이용하면 코드 작성 시 area 항목만 필요하구나 하고 읽는이에게 명시적으로 전달할 수 있게 된다.

 

Console.WriteLine(obj switch
    {
        IFormatProvider fmt => $"{fmt.GetType()} object",
        null => "A null object reference: Its use could result in a NullReferenceException",
        _ => "Some object type without format information"
    });

if (DateTime.TryParse(dateString, out _))
        Console.WriteLine($"'{dateString}': valid");

위 코드와 같이 switch문에서의 default, TryParse의 out 부분 같은 건 필요 없다고 읽는이에게 명시적으로 전달할 수 있다.

 

 

 

코드 생성기 지원

컴파일 프로세스의 일부로 코드를 분석하고 새 소스 코드 파일을 작성(!)할 수 있다.

아니 이런 개꿀기능은 솔직히 글을 새로 파서 알아봐야 한다. 이 글에서는 패스.

반응형