본문 바로가기

프로그래밍 언어 노트/C#

[C#] C# 버전별 주요 변화

C# 6.0이후의 버전들에서 개인적으로 주요한 변화라고 생각하는 변화를 정리해 보았습니다.

비동기 계열은 주요한 변화이긴 하지만 개인적으로 그렇게 자주 사용하지 않고 필요할때 찾아보는 편이라 우선 제외하였고.. 제가 주로 사용하는 기능 위주로 정리하였습니다.

1. Overview

C# 버전 최소 프레임워크 및 VS 버전 주요 변화
C# 6 .NET 4.6 / VS 2015

1. Null 조건 연산자 (?, ??), (옵서널 체이닝)

2. String Interpolation

3. 읽기전용 프로퍼티

4. Expression Member

C# 7 .NET 4.6.2 / VS 2017

1. 패턴매칭 (statement)

2. 튜플

3. 디스트럭팅

4. 로컬함수

5. Expression Member

C# 8 .Net Core 3 / VS 2019

1. 패턴매칭 (expression)

2. 슬라이싱

3. Null 할당자 (??=)

4. 디폴트 인터페이스

5. using 선언

 

2. Null 관련

기능 C# 버전 설명
? 연산자 (C# 6)

널 조건 연산자.

일반적으로 해당 객체에 접근하기위하여 객체.프로퍼티명 과 같이 .(점) 이나 객체[인덱스] 와 같이 인덱서를 사용한다. 이때 객체가 null 인경우 null pointer exception이 발생하기 때문에 if문으로 null 검사를 해야 했다.

C# 6.0 부터는 ? 연산자를 통하여 null 체크없이 접근이 가능하다.

int? value = db?.table?[0].row 와 같이 . 이나 인덱서 앞에 사용가능하다.
위 코드에서 db가 null 이거나 table 이 null 이라면 자동적으로 value 에 null이 할당되고 그렇지 않으면 db.table?.row row가 들어가게된다.

?? 연산자 (C# 6)

널 병합 연산자

??연산자의 경우 앞의 조건이 null인경우 대체값을 리턴해준다.

value ?? default-value 처럼 사용한다. value가 null이라면 default-value가 리턴된다.

예를들어 int? value = db?.table?[0].row 에서 value 는 null 이 될수 있으므로 int? 로 선언되었다.
이때 int value = db?.table?[0].row ?? 0 과 같이 사용하여 db나 table 이 null인 경우 value 에 null 대신 0 이 들어가게 된다.

??= 연산자 (C# 8)

널 병합 할당자

?? 연산자 처럼 null인경우 대체값을 사용하기 위해 사용한다.

값을 할당할 변수자체가 null이라면 값을 할당하게된다.

Class1 value = null
...
if (value == null)
    value  = new Class1()

위와 같은 상황에서 value 가 null 일때 객체가 할당된다.
위의  if 문과 같은 상황을 value ??= new Class1() 으로 단축할수 있다..

3. 튜플

기능 C# 버전 설명
튜플 (C# 7)

복수개의 값을 사용하기위한 자료구조이다.

C# 에서는 일반적으로 두 개 이상의 값을 반환하기 위하여 ref 나 out 등을 사용하였다.
튜플을 사용하면 복수개의 값을 튜플에 담아 튜플 자체를 리턴 할 수 있다.

var unnamed = ("one", "two"); 과 같이 () 안에 , 로 값을 구분한다.
var named = (first: "one", second: "two"); 와 같이 각 요소에 이름을 부여 할 수 있다.


using System;

public class Example
{
    public static void Main()
    {
        var result = QueryCityData("New York City");

        var city = result.Item1;
        var pop = result.Item2;
        var size = result.Item3;
    }

    private static (string, int, double) QueryCityData(string name)
    {
        if (name == "New York City")
            return (name, 8175133, 468.48);

        return ("", 0, 0);
    }
}

아래의 디스트럭팅 기능과 함께 사용하여 값을 보다 유연하게 사용할수있다.

디스트럭팅, 디컨스트럭션 (C# 7)

C# 에서는 Deconstruction 이라고 하는듯하다.

일반적으로 튜플에 사용하고 사용자정의 클래스에도 Deconstruct 메소드가 존재하면 사용가능하다.

위 튜플의 예제 코드에서 리턴받은 튜플의 값에 접근하기 위하여 item1, item2 등을 사용하였다. 이렇게 번거롭게 접근하여 값을 할당 할 필요가 없이


(string city, int population, double area) = QueryCityData("New York City"); 또는
var (city,population, area) = QueryCityData("New York City");
위와같이 변수를 선언하면 자동적으로 튜플이 분해되어 각 요소로 들어간다.

튜플중에 필요 없는 값이 존재하는경우 _ 키워드를 이용하여 버릴수있다.
예를 들어 population 값만 필요하다면
var (_,population,_) = QueryCityData("New York City");
위와 같이 사용할수있다.

4. 패턴 매칭

패턴 매칭 자체에 대한 내용은 https://github.com/Lee-WonJun/FP-Seminar/blob/master/Concept/7.%20Pattern%20Matching%20%26%20Desctuction/Concept%20and%20Example.md 참고

 

Lee-WonJun/FP-Seminar

Contribute to Lee-WonJun/FP-Seminar development by creating an account on GitHub.

github.com

기능 C# 버전 설명
패턴매칭 (Statement) (C# 7)

대상이 특정한 패턴을 가지고 있는가를 확인한다.
기존의 if문 타입 체크나 switch-case 문의 발전형식이다.
const pattern, type pattern, var pattern 이 가능하다.

C# 7.0에서는 기존의 if 문과 switch 문에서 패턴 매칭을 통한 조건 검색이 가능하다.
C# 7.0 부터 switch 에서도 객체를 받아서 분기를 처리할수있게 되었다.

7.0에서 제공되는 패턴 매칭의 경우 Statement 내에서 매칭되는것이므로 변수 할당을 위해서는 패턴 매칭된 구문에서 직접 변수 할당을 해주어야한다.
따라서 향상된 "분기문" 이지 식으로써 쓰일 수 없다.

패턴매칭 (Expression) (C# 8)

보다 많은 매칭이 가능하다. 결정적으로 swtich 구문 "식" 이 추가되었다.

switch 식은 switch 문과 구분하기 위하여 switch를 뒤에쓴다.

public static RGBColor FromRainbow(Rainbow colorBand) =>
    colorBand switch
    {
        Rainbow.Red    => new RGBColor(0xFF, 0x00, 0x00),
        Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
        Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
        _              => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),
    };

5. 기타

기능 C# 버전 설명
String Interpolation (C# 6)

기존에는 string에서 변수를 생성하기 위하여 format 함수를 이용했다면
6.0 버전 부터는 문자열내삽 (String Interpolation) 이 가능하다.

$ 키워드를 문자열 생성시 앞에 붙여주면 문자열 내에서 변수사용이 가능하다.
변수를 사용할때는 {변수명} 을 사용한다.

int name = "LWJ";
string str = $"My name is {name}";
위와 같이 사용하면 str의 결과는  "My name is LWJ" 가 된다.

Expression Member (C# 6,7)

맴버 함수, 프로퍼티 등에 return이 포함된 statement 의 block 대신에 expression 표현이 가능하다.
기존의 람다식 표현방법과 유사하게 선언할수있다.

public int Area => Height * Width;

읽기 전용 프로퍼티 (C# 6) 프로퍼티에서 set을 지우면 자동적으로 읽기전용 프로퍼티가 된다.
public int Area {get; }
로컬함수 (C# 7)

함수 안에 로컬 함수를 선언할수있다.

함수안에서 람다식을 이용하여 사용하는것과 유사하다.
사용하는 측면에서는 거의 유사하다.


https://docs.microsoft.com/ko-kr/dotnet/csharp/local-functions-vs-lambdas

참조.

 

슬라이싱 (C# 8)

python 의 그것과 유사하다.

배열 인덱스로 ^1 등을 사용하여 뒤에서 부터 접근이 가능하다.
배열 인덱스로 .. 을 이용하여 (예를 들어 3..5 ) 슬라이싱이 가능하다.

string word = "Hello World";
char index = word[^1]; // 'd';
string range = word[1..3] // "el"

디폴트 인터페이스 (C# 8)

인터페이스에 기본값을 지정할수있다.

만약 인터페이스를 상속받은 구현체에서 구현을 했다면 구현체의 구현을 따르고 구현을 하지 않았다면 인터페이스의 디폴트 인터페이스를 따른다.

문제는 이렇게 되면 다중상속에의한 죽음의 다이아몬드가 발생할수있다.
python 이나 scala 의 경우 순서에 의한 mixin 방식을 사용하는데
Java 나 C# 에서는 컴파일타임에 오류메시지를 출력해서 구현을 강요한다.

using 선언 (C# 8)

C# 에서는 버퍼, 비트맵 등을 사용할때 using 을 사용하여 자동으로 Dispose 를 호출시킬수있다.

8.0 에서 부터는 using 선언의 범위를 {} 로 지정해주지 않아도 함수가 끝나면 자동적으로 Dispose 가 호출된다.

static int WriteLinesToFile(IEnumerable lines)
{
    using var file = new System.IO.StreamWriter("WriteLines2.txt");
    //어쩌구 저쩌구
    return skippedLines;
    // file is disposed here
}

 

더 자세한 내용은 https://docs.microsoft.com/ko-kr/dotnet/csharp/ 참고.

728x90