1. 디폴트 인터페이스 멤버(Default Inteface Members)
이전 버전에서는 인터페이스를 한번 배포한 후 수정하면, 기존에 구현된 모든 타입들을 수정하지 않는 한 타입 오류를 발생시켰다. 더구나 그 인터페이스를 외부에서 사용한다면, 수정은 거의 불가능하였다. C# 8.0에서는 인터페이스에 새로운 멤버를 추가하고 새로운 멤버의 Body 구현 부분을 추가할 수 있게 되었다. 이렇게 새로 추가된 인터페이스 멤버는 디폴트로 사용되기 때문에 기존 구현된 타입들이 새 멤버를 추가적으로 구현되지 않을 경우 이 디폴트 구현을 사용하게 된다.
- 새로 구현하는 클래스는 디폴트 멤버 구현을 사용하지 않고 재정의할 수 있다.
- 인터페이스의 디폴트 멤버 구현을 액세스 하기 위해서는 인터페이스로 캐스팅된 변수를 사용해야 한다.
// ILogger v1.0
public interface ILogger
{
void Log(string message);
}
// ILogger v2.0
public interface ILogger
{
void Log(string message);
// 추가된 멤버
void Log(Exception ex) => Log(ex.Message);
void Log(string logType, string msg)
{
if (logType == "Error" || logType == "Warning" || logType == "Info")
{
Log($"{logType}: {msg}");
}
else
{
throw new ApplicationException("Invalid LogType");
}
}
}
class MyLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
Debug.WriteLine(message);
}
// 디폴트 구현을 사용하지 않고 새로 정의함
public void Log(Exception ex)
{
Debug.WriteLine(ex.ToString());
}
}
2. 패턴 매칭
1) switch expression
기존 switch 문(switch statement)은 case 별로 값을 체크하여 분기하지만, switch 식(switch expression)은 기존의 case 블록들을 보다 간결하게 식으로 표현한 것이다.
- switch 식은 switch 문과 달리 switch 식 앞에 변수 명을 적고 각 case 블록은 case, break, default 등을 쓰지 않고 (패턴/값) => (수식)과 같은 식으로 표현한다.
static double GetArea(Shape shape)
{
// switch expression
double area = shape switch
{
null => 0, // null 체크
Line _ => 0,
Rectangle r => r.Width * r.Height,
Circle c => Math.PI * c.Radius * c.Radius,
_ => throw new ArgumentException() // default와 같은 기능
};
return area;
}
2) 속성 패턴(Property Pattern)
속성 패턴(Property Pattern)은 객체의 속성을 사용하여 패턴 매칭을 할 수 있도록 한 것이다. 속성 패턴을 사용하면 복잡한 switch 문을 보다 간결하게 switch 식으로 표현할 수 있다.
public decimal CalcFee(Customer cust)
{
// Property Pattern
decimal fee = cust switch
{
{ IsSenior: true } => 10,
{ IsVeteran: true } => 12,
{ Level: "VIP" } => 5,
{ Level: "A", IsMinor: false} => 10,
_ => 20
};
return fee;
}
3) 튜플 패턴(Tuple Pattern)
튜플 패턴(Tuple Pattern)은 하나의 변수가 아닌 여러 개의 변수들에 기반한 패턴 매칭을 말한다.
static int GetCreditLimit(int creditScore, int debtLevel)
{
// Tuple Pattern
var credit = (creditScore, debtLevel) switch
{
(850, 0) => 200,
var (c, d) when c > 700 => 100,
var (c, d) when c > 600 && d < 50 => 80,
var (c, d) when c > 600 && d >= 50 => 60,
_ => 40
};
return credit;
}
static void Main(string[] args)
{
int creditPct = GetCreditLimit(650, 30);
Console.WriteLine(creditPct);
}
4) 위치 패턴(Positional pattern)
만약 어떤 타입이 Deconstructor를 가지고 있다면, Deconstructor로부터 리턴되는 속성들을 그 위치에 따라 패턴 매칭에 사용할 수 있는데, 이를 위치 패턴(Positional pattern)이라 한다.
class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
public void Deconstruct(out int x, out int y) =>
(x, y) = (X, Y);
}
static string 사분면(Point point)
{
// Positional pattern
string quad = point switch
{
(0, 0) => "원점",
var (x, y) when x > 0 && y > 0 => "1사분면",
var (x, y) when x < 0 && y > 0 => "2사분면",
var (x, y) when x < 0 && y < 0 => "3사분면",
var (x, y) when x > 0 && y < 0 => "4사분면",
var (_, _) => "X/Y축",
_ => null
};
return quad;
}
static void Main(string[] args)
{
var p = new Point(-5, -2);
string q = 사분면(p);
Console.WriteLine(q); // 3사분면
}
5) 재귀 패턴(Recursive pattern)
패턴은 다른 서브 패턴(sub pattern)들을 포함할 수 있고 한 서브 패턴은 내부에 또 다른 서브 패턴들을 포함할 수 있는데, 이러한 것을 재귀 패턴(Recursive pattern)이라 한다.
IEnumerable<string> GetStudents(List<Person> people)
{
foreach (var p in people)
{
// Recursive pattern
if (p is Student { Graduated: false, Name: string name })
{
yield return name;
}
}
}
3. Nullable Reference Type
이전 버전에서는 reference 타입에 null을 할당할 수 있어 Null Reference Exception이 자주 발생되곤 했다. 그래서 C# 8.0에서는 reference 타입에 null을 할당하면 컴파일러가 경고하는 기능을 추가되었다.
- reference 타입은 기본적으로 null을 넣을 수 없는 Non-nullable Reference Type이 되고, NULL을 허용하기 위해서는 레퍼런스 타입 뒤에 물음표(?)를 붙여 Nullable Reference Type임을 표시해야 한다.
- Nullable Reference Type 기능은 디폴트로 Disable 되어 있으며 사용하기 위해서는 프로젝트 레벨이나 파일 레벨, 혹은 소스코드 내의 임의의 위치에서 Enable 해야 한다.
static void Main(string[] args)
{
// nullable enable
string s1 = null; // Warning: Converting null literal or possible null value to non-nullable type
if (s1 == null) return;
string? s2 = null;
if (s2 == null) return;
// nullable disable
string s3 = null; // No Warning
if (s3 == null) return;
}
4. 인덱싱과 슬라이싱
1) 인덱싱
System.Index 구조체는 시퀀스의 시작 또는 끝으로부터 인덱싱을 표현하는 데 사용된다.
- ^ prefix 연산자를 사용해 시퀀스를 뒤에서부터 인덱싱할 수 있다. 시퀀스의 끝 인덱스를 ^1, 끝에서 2번째는 ^2, 끝에서 3번째는 ^3과 같이 표시한다.
string s = "Hello World";
// System.Index
Index idx = ^2;
ch = s[idx]; // l
// 예
char ch1 = s[0]; // H
char ch1 = s[1]; // e
char ch2 = s[^1]; // d
char ch2 = s[^2]; // l
2) 슬라이싱
System.Range 구조체는 시작과 마지막 인덱스를 함께 가지며 범위를 표현할 때 사용된다.
- .. 범위 연산자를 사용해 시퀀스의 부분 인덱스를 표시할 수 있다.
- 마지막 인덱스는 실제 범위의 마지막 다음 요소이다. 즉, Range 가 1..4이면 1부터 3까지가 실제 범위가 된다.
string s = "Hello World";
// System.Range
Range r1 = 1..4;
string str1 = s[r1]; // ell
Index start = r1.Start;
bool b = start.IsFromEnd; // false
int v1 = start.Value; // 1
int v2 = r1.End.Value; // 4
// 예
var s1 = s[1..4]; // ell
var s2 = s[^5..^2]; // Wor
var s3 = s[..]; // Hello World
var s4 = s[..3]; // Hel
var s5 = s[3..]; // lo World
Range rng = 1..^0;
var s6 = s[rng]; // ello World
5. using 선언
using 선언은 using 뒤에 있는 변수가 using을 둘러싼 범위를 벗어날 경우 Dispose 하도록 컴파일러에게 지시하게 된다.
- Dispose는 메모리 관리를 위해 사용되며 더 이상 이 오브젝트를 쓰지 않고, 관련 리소스를 정리한다는 뜻이다.
- 메서드가 끝날 때 Dispose를 자동 호출한다.
private void GetDataCS8()
{
using var reader = new StreamReader("src.txt");
string data = reader.ReadToEnd();
Debug.WriteLine(data);
// 여기서 Dispose() 호출됨
}
6. 널 병합 할당자(Null Coalescing Assignment)
흔히 NULL을 먼저 체크하고 NULL이면 어떤 값을 할당하는 코드를 많이 작성해 왔다.
if (list == null)
list = new List<int>();
C# 8.0에서는 더욱 간결하게 널 병합 할당자(Null Coalescing Assignment)인 ??= 연산자를 써서 표현할 수 있게 되었다.
static List<int> AddData(List<int> list, int? a, int? b)
{
list ??= new List<int>();
list.Add(a ??= 1);
list.Add(b ??= 2);
return list;
}
7. 구조체 읽기 전용 멤버
이전 버전에서는 구조체(struct) 전체를 readonly로 만들 수 있었는데, C# 8.0부터는 구조체의 각 멤버에 대해 개별적으로 readonly로 정의할 수 있게 되었다. 만약 구조체의 메서드나 속성이 구조체의 상태를 변경하지 않는다면 readonly로 적용할 수 있고, readonly 멤버가 다른 non-readonly 멤버를 액세스 하면 컴파일러가 Warning을 표시한다.
[C# 8.0] 새로운 기능 (1) - 디폴트 인터페이스 멤버, 패턴 매칭 — 💡번뜩💡 (tistory.com)
[C# 8.0] 새로운 기능 (2) - Nullable Reference Type, 인덱싱과 슬라이싱 — 💡번뜩💡 (tistory.com)
[C# 8.0] 새로운 기능 (3) - using 선언, 널 병합 할당자, 구조체 읽기 전용 멤버 — 💡번뜩💡 (tistory.com)
'프로그래밍 언어 > C#' 카테고리의 다른 글
[C#] String Interning (0) | 2024.01.04 |
---|---|
[C#] 고성능 서버 - __FILE__, __LINE__ 대체제 (0) | 2024.01.04 |
[C#] 9.0 새로운 기능 (0) | 2023.10.03 |
[C#] 4.0 필수 매개변수 및 선택적 매개변수 (0) | 2023.09.22 |
[C#] 생성자와 상속 (0) | 2023.09.22 |