c# 8.0 정리
1.디폴트 인터페이스 멤버 구현 (Default Inteface Members)
지금까지 (C# 8.0 이전) C#에서 인터페이스를 한번 배포한 후, 그 인터페이스를 수정하면 기존에 구현된 모든 타입들을 수정하지 않는 한 타입 오류를 발생시켰다. 더구나 그 인터페이스를 외부에서 사용한다면, 수정은 거의 불가능하였다. C# 8.0에서는 인터페이스에 새로운 멤버를 추가하고, 새로운 멤버의 Body 구현 부분을 추가할 수 있게 되었다. 이렇게 새로 추가된 인터페이스 멤버는 디폴트로 사용되기 때문에, 기존 구현된 타입들이 새 멤버를 추가적으로 구현되지 않을 경우, 이 디폴트 구현을 사용하게 된다.
출처 - https://www.csharpstudy.com/Latest/CS8-def-itf-mem.aspx
예제로 보자
public interface ICharacterBehavior
{
//int Attack(); 기존의 요구조건
int Attack(float attackSpeed); //새롭게 변경된 인터페이스
}
기본적인 캐릭터를 구현할 때 사용할 인터페이스다. 보기에는 문제가 없다. 그러나, 갑자기 패치내역이 생겨
공격력을 계산할 때 추가적으로 공격속도를 매개변수로 받아야할 변경사항이 생기게 되었다.
8.0이전에는 인터페이스를 새로 구현한다면 이전의 Attack 인터페이스를 구현하는 클래스는 오류가 발생한다.
public void Chracter() : ICharacterBehavior
{
//기존의 메서드에 오류 발생!!
//인터페이스의 요구조건인 speed 매개변수가 없음.
int Attack()
{
return 10;
}
}
그래서 해결책으로 나온것이 디폴트 인터페이스
public interface ICharacterBehavior
{
int Attack(); 기존의 요구조건은 그대로 구현
int Attack(float attackSpeed)
{
return 10 * attackSpeed;
}
}
public void Chracter() : ICharacterBehavior
{
//기존의 요구조건은 구현했고, 디폴트 인터페이스가 이미 구현돼있어서 구현할 필요 X
int Attack()
{
return 10;
}
}
2.Nullable Reference Type
기존의 참조타입은 모두 Null이 가능했다.
Ex)
Character character = null; //OK
List<int> list = null; //OK
c#팀은 의도치 않게 Null이 들어간 경우를 최대한 배제하기 위해서앞으로
Character character = null; //경고 메시지 출력
List<int> list = null; //경고 메시지 출력
으로 바꾸기로 했다.
그러면 어떻게 해야하냐고,,? null이 가능한 객체는
타입 앞에 ? 를 붙여주면 경고가 뜨지 않는다.
int number = null; //컴파일 에러
int? number = null; //값 타입도 null 가능
Character character = null; //참조타입이라 컴파일이 가능하지만, 경고메시지
Character? character = null; //경고가 뜨지 않는다.
엥 저는 ? 안붙여도 경고가 뜨지 않는걸요,,? 라고 할 수 있는데 이건 8.0이전 기존의 프로그램들은 ?가 무수히 발생할 것이기 때문에 기본적으로 off로 설정돼있다. on하는 방법은 검색해서 직접 찾아보자.
3.인덱싱과 슬라이싱
미리 간단하게 설명하고 넘어가면
int index,
int[] range,
이런식으로 대부분 지역변수를 선언해서 메서드 내에서 사용하고는 한다.
근데 사람마다 인덱스와 범위의 표현 방식이 다르다보니, c#에서 명확하게 System.Index, System.Range로
선언해놓은 기능이다. (또한 단순한 변수를 넘어서 고유의 기능이 존재한다!!! 단순하게 표현하기 위해 위와같은 표현을 사용)
특이점은 뒤에서부터 x번째를 ^x로 표현한 것이다.
예를 들자면
int a = [1,2,3,4,5]
에서 ^2는 뒤에서 부터 두번째 5다음에 있는 4를 가리킨다.
using System;
class Program
{
static void Main()
{
int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Index lastIndex = ^1; //뒤에서부터 첫번째
Console.WriteLine(numbers[lastIndex]); // 9
Range range = 2..^1; //두번째부터... 뒤에서부터 첫번째
int[] slicedNumbers = numbers[range];
foreach (var number in slicedNumbers)
{
Console.Write(number + " "); // 2 3 4 5 6 7 8
}
}
}
4. 비동기 스트림
Task가 한번이 아닌 특정 계산이 완료될 때마다 하나씩 콜백되어야할 일이 존재한다.
디바이스의 온도나 날씨값이 변했을 경우 리턴한다던지, 아니면 거대한 캐릭터 데이터베이스를 로딩하는데 시간이 오래 소모되어서 하나하나씩 전부 읽는대로 반환한다던지,, 사용할 일은 많을 것이다.
또한 비동기적인 연속된 값을 처리하기 편하게 await foreach 키워드가 추가되었다.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
//비동기적인 GenerateNumbersAsync메서드를 비동기적으로 처리한다.
await foreach (var number in GenerateNumbersAsync())
{
Console.WriteLine(number);
}
}
static async IAsyncEnumerable<int> GenerateNumbersAsync()
{
for (int i = 0; i < 10; i++)
{
// 비동기 작업 수행
await Task.Delay(100);
// 생성한 값을 yield return
yield return i;
}
}
}
5. using 선언
// C# 8.0
private void GetDataCS8()
{
using var reader = new StreamReader("src.txt");
string data = reader.ReadToEnd();
Debug.WriteLine(data);
// 여기서 Dispose() 호출됨
}
// C# 모든 버전
private void GetData()
{
using (var reader = new StreamReader("src.txt"))
{
string data = reader.ReadToEnd();
Debug.WriteLine(data);
} // 여기서 Dispose() 호출됨
// ...
Debug.WriteLine("...");
//출처 https://www.csharpstudy.com/Latest/CS8-using.aspx
}
결론적으로 using키워드를 {}에 집어넣지 않아도 해당 메서드가 끝나면 알아서 Dispose를 호출해주는 기능이다.
아예 if문도 {} 알아서 처리하지 그냥.. 편의성으로는 나쁠 것이 없으나 나의 지극히 개인적인 생각으로는
코드를 변경할 일이 있을 경우를 대비해서 미리 {}에 넣어서 처리하는 것이 좋을 것 같다.
6. 널 병합 연산자
역시 편의성 업데이트다. nullable 형식에서 null 여부를 검사하는 조건문을 줄일 수 있게 되었다.
//기존
if (list == null)
{
list = new List<int>();
}
//8.0 이후
list ??= new List<int>();
이 역시도 편하게 사용할 기능인듯 하다.
7.구조체(struct) 읽기 전용 멤버 및 in
C# 8.0에서는 구조체(struct)의 멤버에 대해 readonly 키워드를 사용하여 개별적으로 읽기 전용으로 정의할 수 있다.
이와 관련하여 in 파라미터라는 새로운 개념도 추가되었습니다. in 파라미터를 사용하면 메서드 호출 시 값을 복사하지 고도 인자를 전달할 수 있다.
using System;
struct Point
{
public readonly double X; //X를 변경할 수 없다
public readonly double Y; //Y를 변경할 수 없다
public Point(double x, double y)
{
X = x;
Y = y;
}
public double DistanceTo(in Point other)
{
double dx = X - other.X;
double dy = Y - other.Y;
return Math.Sqrt(dx * dx + dy * dy);
}
}
class Program
{
static void Main(string[] args)
{
Point p1 = new Point(1, 2);
Point p2 = new Point(3, 4);
double distance = p1.DistanceTo(in p2);
Console.WriteLine($"Distance between p1 and p2: {distance}");
}
}
대체 이게 무슨 의의를 가지는가 ?
In 파라미터를 사용해서 큰 구조체를 복사해서 전달하는 것이 아닌 참조형식으로 전달해서 성능상 이득을 가져온다.
(당연하겠지만 큰 값을 복사하기보단 단순히 주소값만 참조하는 것이 효율적일 것)
8.정적 로컬 메서드
예제부터 보자. 말 그대로 정적 로컬 메서드가 선언이 가능하다.
void MyMethod(int x, int y)
{
static int z = 0;
static void MyLocalFunction(int a, int b)
{
Console.WriteLine($"x: {x}, y: {y}, z: {z}, a: {a}, b: {b}");
}
MyLocalFunction(x, y);
z++;
}
이게 대체 무슨 의의가 있나 ?
1. 함수가 어디에 위치하던 상관 없다.
2. 함수 내부 변수가 static이거나, 매개변수만 사용이 가능하다 (명시적으로 로컬 변수를 사용하지 않겠다는 것을 의미한다)
'프로그래밍 언어 > c#' 카테고리의 다른 글
C# 9.0 레코드 타입 (1) | 2023.05.16 |
---|---|
C# 8.0 Switch expression (0) | 2023.05.14 |
C# Var에 관해서 (0) | 2023.05.01 |
C#7.0 ref local, return, struct, Throw expression (0) | 2023.04.29 |
c#7.0 Tuple, Local Function, out, binary literal, Deconstructor (0) | 2023.04.29 |