msdn 블로그에 게시된 New Features in C# 6포스트를 요약했다. C# 6는 VS 2015 프리뷰와 함께 제공된 버전으로 여러가지 문법 특징이 추가되었다. 이 포스트는 요약이라 내용이 좀 부실할 수 있는데 상세한 내용은 위 포스트를 참고하자.

표현식

nameof

새로운 형태의 문자열로 프로그램 요소의 이름을 간혹 알아내야 할 상황을 마주하는데 그때 사용할 수 있는 표현식이다. 점 표기법(dot notation)을 사용한 경우 가장 마지막 식별자를 반환한다.

if (x == null) throw new ArgumentNullException(nameof(x));
WriteLine(nameof(person.Address.ZipCode)); // prints "ZipCode"

person이 스코프 내에서 타입이 아닌 변수라면 두번째 코드는 허용되지 않는다.

문자열 인터폴레이션

번거로웠던 String.Format()을 간편하게 작성할 수 있다.

var s = String.Format("{0} is {1} year{{s}} old", p.Name, p.Age);
s = "\{p.Name} is \{p.Age} year{s} old";
s = "\{p.Name,20} is \{p.Age:D3} year{s} old"; // 형식 지정
s = "\{p.Name,20} is \{p.Age:D3} year{(p.Age == 1 ? "" : "s")} old"; // 표현식도 쓸 수 있음

Note. 프리뷰 이후 문법이 변경되었다. s = $"{p.Name,20} is {p.Age:D3} year{{s}} old";

Null 조건부 연산자

체이닝 등 호출이 연속적으로 이뤄지는 상황에서 null 확인을 더 쉽게 만드는 연산자다.

int? length = customers?.Length; // customers가 null이면 null 반환
Customer first = customers?[0]; // customers가 null이면 null 반환

// null 병합 연산자인 ??와 함께 사용. customers가 null 일 때, 값은 0
int length = customers?Length ?? 0;

// 뒤에 따라오는 멤버 접근, 엘리먼트 접근 등은 customers가 null이 아닐 때만 호출
int? first = customers?[0].Orders.Count();

// 위와 동일한 표현
int? first = (customers != null) ? customers[0].Orders.Count() : null;

int? first = customers?[0].Orders?.Count(); // 연속으로 사용 가능

다만 ?을 사용한 직후에는 문법적으로 모호함이 있어서 바로 호출을 할 수 없다. 그래서 바로 대리자를 호출할 경우 다음과 같이 작성해야 한다.

if(predicate?(e) ?? false) { ... } // 에러 발생
if(predicate?.Inkobe(e) ?? false) { ... }

이벤트를 작동할 때 다음과 같이 작성할 수 있다.

PropertyChanged?.Invoke(this, args);

이 문법은 스레드 안정성을 보장하는데 좌측을 평가한 후 값을 임시로 저장하기 때문이다.

인덱스 이니셜라이저

개체 이니셜라이저를 확장에 다음과 같이 인덱스를 넣을 수 있게 되었다. 표현식 하나로 JSON 개체를 만들 때 유용하다.

var numbers = new Dictionary<int, string> {
  [7] = "seven",
  [9] = "nine",
  [13] = "thirteen",
};

확장 Add 메소드

확장 Add 메소드를 컬렉션 이니셜라이저에서 사용할 수 있다. 예전엔 인스턴스 메소드만 Add를 호출할 수 있었다.

오버로드 향상

오버로드 확인(resolution)이 향상되었다. 상세 내역은 소개되지 않았고 nullable 값 타입을 받을지 말지, 메소드 그룹을 대리자로 받는 등의 향상이 있을 것이라고 한다.

표현문

예외 필터

CLR 호환으로 VB와 F#에만 있던 기능이 이제 추가되었다.

try { ... }
catch (MyException e) if (myfilter(e))
{
  ...
} // if 문이 참일 때만 `catch` 블럭이 실행된다.

필터는 일반적이고 수용할 수 있는 형태로 “오용”을 할 수 있다. 파생작업을 위해 다음과 같이 사용할 수 있다.

private static bool Log(Exception e) { /* log it */; return false; }
...
try { ... }
catch (Exception e) if (Log(e)) {}

catch/finally 블럭에서의 Await

Resource res = null;
try
{
    res = await Resource.OpenAsync( ... ); // 원래 되던 부분
}
catch(ResourceException e)
{
  await Resource.LogAsync(res, e); // 이제 가능한 부분
}
finally
{
  if (res != null) await res.ColseAsync(); // 여기서도 가능
}

맴버 선언

자동 프로퍼티 이니셜라이저

필드에 이니셜라이져 하는 것과 비슷하다. 이 방법으로 이니셜라이징 하면 setter를 거치지 않고 내부 형식 바로 저장이 된다.

public class Customer
{
  public string First { get; set; } = "Jane";
  public string Last { get; set; } = "Doe";
}

Getter only 자동 프로퍼티

setter 없이 자동 프로퍼티를 사용하는게 허용된다. 이 방식은 readonly로 암묵적인 선언이 된다.

public class Customer
{
  public string First { get; } = "Jane";
  public string Last { get; } = "Doe";
}

위와 같이 초기화 하지 않는 경우에는 다음과 같이 타입 생성자에서 선언하면 값이 내부 형식에 바로 저장된다.

public class Customer
{
  public string Name { get; };
  public Customer(string first, string last)
  {
    Name = first + " " + last;
  }
}

이 문법은 표현 타입을 더 간소하게 만든다. 하지만 변형 가능한 타입과 불변 타입의 차이가 없어진다. 변형 가능한 클래스도 상관이 없다면 자동 프로퍼티를 기본으로 사용하는 것도 좋다. 여기다 getter only 자동 프로퍼티는 변형 가능한 타입과 불변 타입을 더 비슷하게 만든다.

표현-본문 함수 멤버

표현-본문 함수 멤버는 메소드와 프로퍼티, 다른 종류의 함수 멤버들의 본문을 블럭이 아닌 람다처럼 표현식을 바로 쓸 수 있도록 지원한다. 실제로 람다처럼 람다 화살표로 작성한다. 블럭 본문에 단일 반환값을 가진 형태와 동일하다.

public Point Move(int dx, int dy) => new Point(x + dx, y + dy);
public static Complex operator +(Complex a, Complex b) => a.Add(b);
public static implicit operator string(Person p) => $"{p.First} {p.Last}";

void를 반환하는 메소드, Task를 반환하는 비동기 메소드에서도 동일하게 사용할 수 있지만, 이 경우에는 람다에서와 같이 꼭 문 표현식(statement expression)이 사용해야 한다.

public void Print() => Console.WriteLine(First + " " + Last);

프로퍼티와 인덱서도 다음과 같이 쓸 수 있다. get 키워드가 없는 대신 표현식 본문 문법에 따라 암묵적으로 사용된다.

public string Name => First + " " + Last;
public Customer this[long id] => store.LookupCustomer(id);

파라미터 없는 구조체 생성자

파라미터 없는 구조체 생성자가 허용되었다. 다음 구조체에서 new Person()은 선언된 생성자에 따라 기본값을 제공한다. default(Person)로 기본값을 사용하거나 new Person[...] 형태로 배열을 사용하면 해당 생성자는 실행되지 않는다. 명시적으로 구조체 타입과 함께 new 키워드를 사용했을 때만 해당된다.

struct Person
{
  public string Name { get; }
  public int Age { get; }
  public person(string name, int age) { Name = name; Age = age; }
  public Person() : this("Jane Doe", 37){ }
}

임포트

using static

using 으로 정적 멤버 타입을 스코프에서 직접 사용할 수 있다. 프리뷰에서는 정적 클래스의 멤버만 불러올 수 있다.

using System.Console;
using System.Math;

class Program
{
  static void Main()
  {
    WriteLine(Sqrt(3*3 + 4*4));
  }
}

Note. 다음과 같이 디자인이 변경되었다.

  1. using에서 using static으로 변경
  2. 구조체나 enum과 같은 멤버의 비정적 타입을 임포트 할 수 있음
  3. VB 모듈의 멤버나 F#의 최상위 함수를 임포트 할 수 있음
  4. 최상위 스코프에서 확장 메소드는 임포트 할 수 없음. 확장을 해온 원 클래스는 불러오지 않고 확장 메소드만 불러오면 안되기 때문.

위 모든 기능들은 VS 2015 프리뷰에서 확인할 수 있다.


이 글을 뒤늦게 확인하고 C#6의 문법적인 변경점을 살펴봤다. (아직 기본적인 문법도 잘 모르긴 하지만.) C# 개발은 얼마 해보지 못했는데 문자열 인터폴레이션만 봐도 편리한 기능들이 많이 나오는구나 느낄 수 있었다. 이번 달 아니면 다음 달 내로 베타가 시작될 것 같은데 기대된다.

색상을 바꿔요

눈에 편한 색상을 골라보세요 :)

Darkreader 플러그인으로 선택한 색상이 제대로 표시되지 않을 수 있습니다.