티스토리 뷰
C# 9.0이 릴리즈 되었습니다. 어떤 기능이 추가되었는지 블로그 포스트를 참고해서 간단하게 작성해 보았습니다.
C# 9.0 on the record | .NET Blog (microsoft.com)
Init-only properties
일반적으로 객체를 생성하는 간단한 방법이 있습니다. 특히 이 방법은 전체 객체 트리를 한번에 생성할 수 있어서 매우 유용합니다.
var person = new Person { FirstName = "Mads", LastName = "Torgersen" };
객체를 만들 클래스는 몇개의 간단한 속성을 가지고 있습니다
public class Person
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
}
그런데, 여기에는 큰 제한이 있습니다. 객체 생성시 데이터를 입력하기 위해서는 프로퍼티의 속성이 변경 가능해야 한다는 것입니다. 그래서, 초기화 전용 속성인 init를 추가했습니다.
public class Person
{
public string? FirstName { get; init; }
public string? LastName { get; init; }
}
이 방법을 사용하면 위의 클라이언트 코드는 여전히 유효하지만 프로퍼티에 추후 값을 변경할때는 오류가 발생합니다.
var person = new Person { FirstName = "Mads", LastName = "Nielsen" }; // OK
person.LastName = "Torgersen"; // ERROR!
따라서, 초기화 전용 속성은 초기화가 완료되면 객체의 상태 변화를 막을 수 있습니다.
Init accessors and readonly fields
init 접근자는 초기화 중에만 호출 할 수 있기 때문에 클래스의 읽기 전용 필드를 변경할 수 있습니다.
public class Person
{
private readonly string firstName = "<unknown>";
private readonly string lastName = "<unknown>";
public string FirstName
{
get => firstName;
init => firstName = (value ?? throw new ArgumentNullException(nameof(FirstName)));
}
public string LastName
{
get => lastName;
init => lastName = (value ?? throw new ArgumentNullException(nameof(LastName)));
}
}
Records
고전적인 객체 지향 프로그래밍의 핵심은 객체가 강력한 정체성을 가지고 있으며, 시간이 지남에 따라 진화하는 가변 상태를 캡슐화한다는 아이디어입니다. C#은 항상 이를 잘 수행했지만, 때로는 정반대를 원할 때가 있습니다. 여기서 C#의 기본값은 작업을 방해하는 경향이있어, 작업이 매우 힘들어졌습니다. 전체 객체가 불변하고 값처럼 동작하기를 원하면 이를 Records로 선언하는 것을 고려해야 합니다.
public record Person
{
public string? FirstName { get; init; }
public string? LastName { get; init; }
}
record는 여전히 클래스이지만, record 키워드는 몇가지 추가 값 유사 동작을 포함합니다. 일반적으로 기록은 identity가 아닌 value로 정의됩니다. 이와 관련하여 record는 구조체에 훨씬 더 가깝지만 record는 여전히 reference type입니다.
record는 변경 가능하지만 주로 변경 불가능한 데이터 모델을 더 잘 지원합니다.
With-expressions
불변 데이터로 작업 할 때 일반적인 패턴은 기존 값에서 새 값을 만들어 새 상태를 나타내는 것입니다. 예를 들어, 우리가 성을 변경하는 경우 다른 성이있는 경우를 제외하고 이전 개체의 복사 본을 만들어 새 개체를 사용합니다. 이 기술을 종종 비파괴 돌연변이라고 합니다. 시간이 지남에 따라 사람을 나타내는 대신 record는 주어진 시간에 사람의 상태를 나타냅니다. 이러한 스타일의 프로그래밍을 돕기 위해 record는 새로운 종류의 표현인 with를 허용합니다.
var person = new Person { FirstName = "Mads", LastName = "Nielsen" };
var otherPerson = person with { LastName = "Torgersen" };
With-expression은 객체 초기화 구분을 사용하여 이전 객체와 새 객체의 차이점을 표현합니다. 여러 속성을 지정할 수도 있습니다.
With-expression은 실제로 이전 객체의 전체 상태를 새 객체에 복사 한 다음 객체 초기화를 하는 방식으로 작동합니다. 즉, 프로퍼티에는 with-expression에서 변경 될수 있는 init 또는 set 접근자가 있어야 합니다.
Value-based equality
모든 객체는 객체 클래스에서 가상 Equals(object) 메서드를 상속합니다. 두 매개 변수가 모두 null이 아닌 경우 Object.Equals(object, object) 정적 메서드의 기반으로 사용됩니다. 구조체는 "값 기반 같음"을 가지도록 재정의하고 Equals를 재귀적으로 호출하여 구조체의 각 필드를 비교합니다. record도 마찬가지로 동작합니다. 이는 "value-ness"에 따라 두 record 객체가 동일한 객체가 아니어도 서로 동일 할 수 있을을 의미 합니다. 예를 들어 수정 된 사람의 성을 다시 수정하는 경우 :
var originalPerson = otherPerson with { LastName = "Nielsen" };
이제 ReferenceEquals(person, originalPerson) = false이지만 Equals(person, originalPerson) = true가 됩니다. 값 기반 Equals와 함께 값 기반 GetHashCode() 재정의도 있습니다. 또한 record는 IEquatable<T>를 구현하고 == 및 != 연산자를 오버로드하므로 값 기반 동작이 모든 서로 다른 같음 메커니즘에서 일관되게 표시됩니다.
Inheritance
Record는 다른 record에서 상속 할 수 있습니다.
public record Student : Person
{
public int ID;
}
With-expression 및 value equality는 정적으로 알려진 유형 뿐만 아니라 전체 런타임 객체를 고려한다는 점에서 record 상속과 잘 작동합니다. Student를 생성하지만 Person 변수에 저장한다고 가정합니다.
Person student = new Student { FirstName = "Mads", LastName = "Nielsen", ID = 129 };
With-expression은 여전히 전체 객체를 복사하고 런타임 유형을 유지합니다.
var otherStudent = student with { LastName = "Torgersen" };
WriteLine(otherStudent is Student); // true
같은 방식으로 값이 같으면, 두 객체의 런타임 유형이 동일한지 확인한 다음 모든 상태를 비교합니다.
Person similarStudent = new Student { FirstName = "Mads", LastName = "Nielsen", ID = 130 };
WriteLine(student != similarStudent); //true, since ID's are different
Positional records
위치적인 접근 방식이 유용할 때가 있습니다. 여기서 그 내용은 생성자 인수를 통해 제공되고 위치 분해를 통해 추출 할 수 있습니다. record에서 자체 생성자와 해체자를 지정하는 것은 완벽하게 가능합니다.
public record Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
public Person(string firstName, string lastName)
=> (FirstName, LastName) = (firstName, lastName);
public void Deconstruct(out string firstName, out string lastName)
=> (firstName, lastName) = (FirstName, LastName);
}
그러나, 정확히 동일한 것을 표현하기 위한 훨씬 더 짧은 구문이 있습니다.
public record Person(string FirstName, string LastName);
이것은 공용 초기화 전용 자동 속성과 생성자 및 해체자를 선언하므로 다음과 같이 사용할 수 있습니다.
var person = new Person("Mads", "Torgersen"); // positional construction
var (f, l) = person; // positional deconstruction
생성 된 자동 속성이 마음에 들지 않으면 같은 이름의 고유 한 속성을 대신 정의 할 수 있으며, 생성 된 생성자와 해체자는 해당 속성을 사용합니다. 이 경우 매개 변수는 초기화에 사용할 수 있는 범위에 있습니다. 예를 들어 FirstName을 보호 된 속성으로 사용하고 싶다고 가정 해 보겠습니다.
public record Person(string FirstName, string LastName)
{
protected string FirstName { get; init; } = FirstName;
}
Positional record는 다음과 같은 기본 생성자를 호출 할 수 있습니다.
public record Student(string FirstName, string LastName, int ID) : Person(FirstName, LastName);
Top-level programs
C#으로 간단한 프로그램을 작성하려면, 상당한 양의 상용구 코드가 필요합니다.
using System;
class Program
{
static void Main()
{
Console.WriteLine("Hello World!");
}
}
이것은 언어 초보자에게 어렵고 느끼게하고, 코드를 복잡하게 만드는 들여 쓰기 수준을 추가하게 됩니다. 그래서, C# 9.0에서는 Top-level 에서 기본 프로그램을 작성할 수 있습니다.
using System;
Console.WriteLine("Hello World!");
모든 코드가 허용됩니다. 프로그램은 using 후와 파일의 유형 또는 네임 스페이스 선언 전에 작성되어야하며, Main 메서드가 하나만 있을 수 있는 것처럼 하나의 파일에서만 그 작업을 수행 할 수 있습니다. 상태코드를 반환하려면, 그렇게 할 수 있습니다. 작업을 기다리고 싶다면, 그렇게 할 수 있습니다. 명령 줄 인수에 액서스하려면 args를 사용할 수 있습니다.
using static System.Console;
using System.Threading.Tasks;
WriteLine(args[0]);
await Task.Delay(1000);
return 0;
로컬 함수는 명령문의 한 형태이며 Top-level 프로그램에서도 허용됩니다. 대신 외부에서 호출하는 것은 오류를 발생합니다.
Improved pattern matching
몇 가지 새로운 종류의 패턴이 C#9.0에 추가되었습니다. 패턴 일치 튜토리얼의 이 코드를 살펴 보도록 하겠습니다. 자세한 사항은 여기를 참고하세요.
public static decimal CalculateToll(object vehicle) =>
vehicle switch
{
...
DeliveryTruck t when t.GrossWeightClass > 5000 => 10.00m + 5.00m,
DeliveryTruck t when t.GrossWeightClass < 3000 => 10.00m - 2.00m,
DeliveryTruck _ => 10.00m,
_ => throw new ArgumentException("Not a known vehicle type", nameof(vehicle))
};
Simple type patterns
이전에는 형식 패턴이 형식이 일치 할 때 식별자를 선언해야 했습니다. 위의 DeliveryTruck _에서와 같이 해당 식별자가 폐기 _ 인 경우에도 마찬가지입니다. 그러나 이제는 아래와 같이 사용할 수 있습니다.
DeliveryTruck => 10.00m,
Relational patterns
C#9.0에는 관계 연산자 <, <= 등에 해당하는 패턴이 도입되었습니다. 따라서 이제 위 패턴의 DeliveryTruck 부분을 중첩 된 스위치 표현식으로 작성할 수 있습니다.
DeliveryTruck t when t.GrossWeightClass switch
{
> 5000 => 10.00m + 5.00m,
< 3000 => 10.00m - 2.00m,
_ => 10.00m,
},
Logical patterns
마지막으로, 식에 사용되는 연산자와의 혼동을 피하기 위해 패턴을 논리 연산자와 결합 할 수 있습니다. 예를 들어, 위의 중첩 스위치의 경우는 다음과 같이 오름차순으로 배치 될 수 있습니다.
DeliveryTruck t when t.GrossWeightClass switch
{
< 3000 => 10.00m - 2.00m,
>= 3000 and <= 5000 => 10.00m,
> 5000 => 10.00m + 5.00m,
},
not null => throw new ArgumentException($"Not a known vehicle type: {vehicle}", nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
또한 다루기 힘든 이중 괄호 대신 is-expression을 포함하는 if 조건에서 편리하게 사용할 수 있습니다.
if (!(e is Customer)) { ... }
상당히 불편하게 느꼈던 부분을 아래 처럼 변경할 수 있습니다.
if (e is not Customer) { ... }
실제로 is not expression은 Customer와 같은 type을 비교할 때도 사용할 수 있습니다.
if (e is not Customer c) { throw ... } // if this branch throws or returns...
var n = c.FirstName; // ... c is definitely assigned here
Target-typed new expressions
"Target-typing"은 표현식이 사용되는 컨텍스트에서 유형을 가져올 때 사용하는 용어입니다. 예를 들어 null 및 람다식은 항상 대상 형식입니다. C#의 새 식에는 항상 형식을 지정해야합니다. C# 9.0에서는 표현식이 할당되는 명확한 유형이 있는 경우 유형을 생략 할 수 있습니다.
Point p = new (3, 5);
이것은 배열이나 객체 초기화에서 반복이 많은 경우 매우 좋습니다.
Point[] ps = { new (1, 2), new (5, 2), new (5, -3), new (1, -3) };
Covariant returns
파생 클래스의 메서드 재정의에 기본 형식의 선언 보다 더 구체적인 반환 형식이 있음을 나타내는 것이 유용할 때가 있습니다. C# 9.0에서는 다음이 가능합니다.
abstract class Animal
{
public abstract Food GetFood();
...
}
class Tiger : Animal
{
public override Meat GetFood() => ...;
}
And much more…
C# 9.0 기능의 전체 세트를 확인하기 위해 가장 좋은 곳은 "C# 9.0의 새로운 기능" 문서 페이지 입니다.
'Visual Studio' 카테고리의 다른 글
Announcing the Release of the Git Experience in Visual Studio (0) | 2020.12.06 |
---|---|
What's New in Visual Studio 2019 version 16.8 (0) | 2020.11.26 |
Announcing .NET 5.0 RC1 (0) | 2020.10.08 |
What's new in ML.NET (0) | 2020.06.17 |
Modernizing .NET Desktop Applications with .NET Core (0) | 2019.10.07 |
- Total
- Today
- Yesterday
- #prism
- Always Encrypted
- #uwp
- ef core
- Microsoft
- WPF
- #MVVM
- .net
- visual studio 2019
- uno platform
- IOT
- .net 5.0
- Bot Framework
- dotNETconf
- Build 2016
- XAML
- UWP
- Windows 10
- MVVM
- uno-platform
- ComboBox
- windows 11
- PRISM
- Visual Studio 2022
- LINQ
- kiosk
- Cross-platform
- C#
- #Windows Template Studio
- Behavior
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |