특히나 이런 배열과 관련한 역순 인덱스나, 슬라이싱은 정말 코드를 깔끔하게 만들어줍니다..
배열 시퀀스의 끝에서부터 데이터를 가져오기
- C# 8.0부터 사용가능
- ^ 를 이용해 인덱스 끝에서 데이터를 접근할 수 있다. 예를 들어 ^1 은 배열 끝에서 첫번째 값에 접근한다.
string message = "Hello World!"
log(message[^1]) // ! 출력
log(message[^2]) // d 출력
log(message[message.Length-1]) // 기존 방식
System.Index index = ^1; // index는 이 구조체에 할당 가능하다.
배열 슬라이싱
- C# 8.0부터 사용가능
파이썬에서도 이미 : 로 같은 방식으로 슬라이싱을 지원한다. C#에서도 이 슬라이싱 기능이 추가되었다. .. 를 이용해 시작 인덱스와 길이를 입력한다.
예를들어 1..4 는 배열[1] 부터 배열[3] 까지 슬라이스한다.
string message = "Hello World!";
log(message[0..5]) // Hello (0,1,2,3,4)
log(message[6..(6+5)]) // World, 이처럼 쓸 수도 있다.
슬라이싱은 항상 새로운 배열을 리턴하기 때문에배열 복사 등에도 활용할 수 있다.
int[] a = new int[4] {1,2,3,4};
int[] b = a;
int[] c = a[..];
a[0] = 20;
Console.WriteLine(a[0]); // 20이 출력됩니다.
Console.WriteLine(b[0]); // a를 참조하므로 20이 출력됩니다.
Console.WriteLine(c[0]); // 복사된 값이므로 원본 1이 그대로 출력됩니다.
이진수로 값을 표현하는 리터럴 0b
- C# 8.0부터 사용가능
- 이진수를 실제로 쓸 일은 적지만 _ 구분자를 알려주고 싶었다(...)
0b 로 시작하는 값은 이진수 리터럴처럼 쓸 수 있다.
int a = 0b10000001;
int b = 0b_1000_0001; // 변수 a와 같다. 언더스코어를 이용해서 구분을 쉽게할 수 있다.
int c = 123_456; // 이진수 리털과는 무관하지만 언더스코어는 자릿수 구분을 위해 활용할수도 있다.
int a = 16; // 16
int xor = 0b_0101_1011_0010;
int encrypt = a ^ xor;
int decrypt = encrypt ^ xor;
Console.WriteLine(encrypt); // xor 암호화된 값 : 1442
Console.WriteLine(decrypt); // xor 복호화된 값 : 16
Null 병합 할당
- C# 8.0부터 사용가능
Null 병합 할당을 간단하게 수행할 수 있다.
if(list == null)
list = new List<int>()
이 코드를 아래와같이 축약할 수 있다.
list ??= new List<int> ();
불변성 데이터셋을 record 로 관리
불변성이란 말 그대로 변하지 않는 데이터를 뜻한다. C#에서는 struct 나 class를 사용해서 만든 값을 불변하게 하고싶으면 모든 멤버에 readonly를 붙여서 구현할 수 있겠지만 아예 내부 참조값을 변경할 수 없는 record 키워드가 있다.
이 키워드를 사용하면 명시적으로 타입이 불변함을 C#컴파일러에게 알려줄 수 있다. 예를 들어 게임에서 리플레이 데이터는 반드시 불변적이어야한다. 이미 기록된 데이터를 수정해서도 안되고 어딘가에 파라미터로 넘겼을때 곳에서도 수정되어서도 안된다.
public record ReplayData{
public DateTime Time { get; }
public int Action { get; }
public ReplayData(DateTime time, int action)
=> (Time, Action) = (time, action);
}
var replayData = new ReplayData(DateTime.Now, 1);
replayData.Action = 10; // Error!
replayData = new ReplayData(DateTime.Now, 10); // 허용
상속도 할 수 있다.
public record MonsterReplayData : ReplayData{
public Vector3 position;
public int health;
}
게임 리플레이 데이터가 와닿지 않는다면 서버로부터 받은 데이터 를 생각해보면 좋다.
Target Typeing new()
new() 키워드는 코드의 문맥으로부터 타입을 추론할 수 있다. 아래 코드에서는 hash라는 변수 에 값을 할당하게 된다면 그 값은 상속된 딕셔너리 이거나 혹은 Dictionary<string, string> 타입이 확실하다는것을 코드의 문맥상 추론할 수 있다.
private Dictionary<string, string> hash = new();
// 많은 분들이 상황별 키워드 var 타입을 알고있을것이다. var또한 타입을 우측에 명시된 타입으로
// 추론하고 IL 컴파일 결과도 동일하다.
// 다만 var타입은 일반적으로 함수내부의 지역변수 혹은 스크립트 코드에서만
// 사용 가능하다.
void Something()
{
var hash2 = new Dictionary<string,string>();
}
위에서 배운 NULL 할당 연산과 활용 가능하다
// hash가 null인경우 생성
private PlayerData data;
void Awake(){
data ??= new();
}
Out 키워드 축약
Call(out var outVal);