Open

C# 코드를 깔끔하게 유지하기 위한 유용한 초급문법들

shlifedev 2023. 2. 7. 00:16
실무에서 쓰기에 매우 유용한 문법들을 모아두었습니다.
 
고급문법이 아니며 유용하고 쉽게 사용할 수 있습니다.
 
 
아래 코드들을 잘 활용하시면 코드가 정말 크게 깔끔해질거에요. 
 
 
 
 
배열 시퀀스의 끝에서부터 데이터를 가져오기 
 
 
 
아래 코드는 변수 apb의 마지막 값을 가져오기위해 두번이나 참조 해야해서 기분이 나쁩니다.
 
뭔가 찝찝하지요.
string apb = "abcdefg";
string g = apb[apb.Length-1]​
  • 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> ();

* 팁을 주자면 유니티에서는 이런것도 가능하다. 기존 싱글톤의 get 프로퍼티의 if문을 매우 깔끔하게 정리가능하다.

private static Something _instance;
public static Something Instance {
   get{
           _instance??=GetComponent<Something>();
           return _instance
   } 
}

* 하나 더 예를 들자면 Awake 에서의 초기화이다.

void Awake(){
    a??=GetComponent<...>
    b??=...
}


if로 더이상 null check 하지말자.

 

 

불변성 데이터셋을 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);