C# 6.0 추가된 기능들

냐옹·2024년 4월 12일
0

.NET

목록 보기
28/31

C# 6.0

표현식멤버 Expression-bodied-member

속성이나 메서드의 Body 블럭이 간단한 경우에, Statement Block을 사용하는 대신 간단한 함수식 (expression)을 사용할 수 있다. 이를 표현식멤버라고 한다.
람다식과 유사하다.

기존코드
public int Area{
	get{
    	return Height * Width;
    }
}
기능추가 후
public int Area => Height * Width;

// 메서드에서 하나의 Point 개체 리턴
public Point Move(int x, int y) => new Point(X+x, Y+y);

// 메서드에서 void 리턴
public void Print() => Console.WriteLine(data);

// 속성에서 get 리턴
public string Name => FirstName + " " + LastName;

// 인덱서에서 Customer 개체 리턴
public Customer this[int id] => db.FindCustomer(id);

// 연산자 메서드 표현
public static Complex operator +(Complex a, Complex b) => a.Add(b);

읽기전용 자동 속성

이를 통해서 변경불가능 속성 (Immutable Property)를 간단하게 구현할 수 있게 되었다.

예시코드
class Person{
	public string Name { get; set; } = "No Name";
    
    // 초기화를 하지 않았을 때는 default 값이 사용된다.
    public string NickName { get; } // null
    public int Age { get; } // 0
    
    // Auto-Property Initializer 할당
    public bool Enabled { get; } = true;
    
    // 생성자에서 초기값 할당
    public int Level{ get; }
    public Person(){
    	this.Level = 1;
    }
}

자동속성 초기자

원래 속성을 작성할때 이렇게 하지 않는가?

class A{
	public string Name{
    	private string _name = string.Empty;
        get{ return _name; }
        set{ this._name = value; } 
    }
}

이걸 갖다가

class A{
	public string Name{
    	get;set;
    } = string.Empty;
}

catch/finally 블럭에서 await 사용

기존에 C# 5.0에서 await 기능을 도입할 때, catch나 finally 블록에서 await을 사용하는 기능을 지원하지 않았고, 개발자들은 여러 WorkAround를 사용했어야 했는데 C# 6.0부터 사용가능하게 되었다.

Catch 블럭에서 일반적으로 에러를 로깅을 하는 경우가 많은데, 이 때 로깅처리를 비동기적으로 하기 위해서 await을 사용할 수 있다.

예시코드
IDbConnection conn;
try{
	var response = await req.GetResponseAsync();
}
catch(Exception ex)
{
	await Log(ex);
}
finally
{
	await Close(conn);
}

Exception Filter

Catch 시에 특정한 조건으로만 다시 필터링하여 Catch 하는 것으로 C# 6.0이후의 문법에서는 Catch 문 뒤에 추가적인 when 조건문을 사용한다.

try{
	// ..
}
catch(Exception ex) when(ex.NativeErrorCode == 0x10)
{
	//
}

Dictionary Initialin lizer

기존 C#에서 딕셔너리를 초기화하는 스타일초기화 후 사용하는 스타일 간에 약간의 차이가 있었다. 딕셔너리를 사용할 때 기존에는 []을 쓰고 선언시에는 []을 쓰지 않았는데 통일했다는 점에서 의의가 있다.

// 처음 초기화 시 ( 이전 )
var scores = new Dictionary<string, int>(){
	{"namellllllllllll
}

int sc = scores["lee"]

// C#6 ~ 
var scores = new Dictionary<string,int>(){
	["kim"] = 100,
    ["lee"] = 90
}

int sc = scores["lee"];

널 조건 연산자

?, ?.
널 조건 연산자는 ? 앞에 있는 개체가 null인지 체크해서 null 이면 그냥 null을 리턴하고 그렇지 않으면 ? 다음의 속성이나 메서드를 실행한다. 이렇게 하면 일일히 if 문을 써서 null 체크할 필요 없이 축약해서 개발자의 의도를 표현할 수 있다.
인덱서나 배열에서는 ?[]로 표현할 수 잇다.

예시
// rows가 null이면 cnt도 null
// row가 null이 아니면 cnt는 실제 rows 개수
int? cnt = rows?.Count;

// customers 컬렉션이 null이면 c는 null
// 아니면 c는 첫번째 배열요소
Customer c = customers?[0]

// customers가 널인지 체크하고
// 다시 customer[0]이 null인지 체크
int? age = customers?[0]?.Age;
    

??연산자와 함께 사용

널 조건 연산자만을 사용하게 되면, 리턴 변수는 항상 null을 가질 수 있는 Nullable 타입이어야 한다. (ex : int?, double?)

?.연산자 앞의 값이 null일 경우에 null을 리턴해야하기 때문이다. 만약에 리턴 변수가 null을 가질 수 없는 경우라면? 혹은 null이 아니어야 한다면 ??연산자를 쓰면 된다. null 일 경우에 ?? 뒤의 디폴트 값을 리턴하는 것이다.

이렇게 하면 받는 타입이 널가능형일 필요가 없다.

<이벤트 호출 시 활용> : 잘보기

널 조건 연산자를 활용하는 대표적인 예는 이벤트를 호출 : Fire하는 것이다.
기존의 C#에서 이벤트를 Fire하는 루틴은 크게 3가지 단계가 있었다.

아래의 단계는 이벤트 핸들러가 여러 스레드에 의해서 등록되거나 취소될 수 있으므로, 이벤트 호출 시 이벤트 핸들러 목록이 변경되지 않도록 하는 것이다.

(1) 이벤트 필드를 메서드 내에서 로컬 변수에 할당한다

(2) 다음 로컬 이벤트 변수가 널인지 체크한다.

(3) 마지막으로 이벤트를 Invoke 한다.

여기서 첫번째 이벤트를 로컬 변수에 할당하는 이유는 스레드안전 때문이다.
만약에 이 할당을 하지 않으면, 두번째 단계인 널 체크가 끝나고 세번째 Invoke 단계에 들어가기 직전에 다른 스레드가 이벤트 핸들러를 Unsubscribe할 경우 이벤트 필드가 Null이 되어 널참조가 발생할 수 있기 때문이다.

  • 이벤트 필드는 모든 스레드가 엑세스할 수 있지만, 메서드 로컬 변수는 각 스레드에서 베타적으로 작용한다.

아래 코드를 보자

public class MyButton{
	public event EventHandler Clicked;
    
    // 이전 방식
    public void Click1(){
    	// -- 1단계 --
    	var tempClicked = Clicked;
        // -- 2단계 --
        if( tempClicked != null ){
        	// -- 3단계 -- 
            tempClicked(this, null);
        }
    }
    
    // C# 6.0 방식
    public void Click2(){
    	Clicked?.Invoke(this, null);
    }
}

해당 글은 https://www.csharpstudy.com/Latest 를 참고하여 작성하였음을 알립니다. 이 글의 목적은 본인의 학습복습입니다.

0개의 댓글