끝나지 않는 프로그래밍 일기

C# 6.0에서는 어떤 기능들이 추가되었을까? 지금부터 새로 추가된 9가지의 기능들을 함께 살펴보도록 하자.



using 정적 지시문(using static)

기존에는 정적 메서드를 호출하거나 속성을 가져오기 위해서는 아래와 같이 클래스명과 메서드명 또는 속성명을 같이 적어주어야 했다.

double result = Math.Sqrt(a);

그럼 여기에서 아래와 같은 문장을 추가하면 어떻게 될까?

using static System.Math;

직접 예제를 보도록 하자.

using static System.Math;

namespace StaticUsingExample
{
    class Program
    {
        static void Main(string[] args)
        {
            double a = 4;
            double result = Sqrt(a);
        }
    }
}

보니까 클래스명을 생략한 채로 Math.Sqrt() 메서드를 호출할 수 있게 되었다. 'using static 클래스명;'을 적은 후에는, 그 클래스의 정적 메소드나 속성들을 클래스명 표기 없이 사용할 수 있게 되었다.


자동 속성 초기자(Auto-property initializers)

자동 속성(auto-property)은 C# 3.0에서 추가된 기능으로, 실제로 읽고 쓰여지는 private 필드인 backing field(혹은 backing store)를 컴파일러가 알아서 만들어주는 기능이다. 기억이 잘 나지 않는 사람들을 위해 다시 한번 살펴보도록 하자.

private string _name = "홍길동"; // backing field
public string Name
{
    get { return _name; }
    set { _name = value; }
}

위의 코드에서 자동 속성을 이용하면 아래와 같이 바꿀 수 있다.

class MyClass
{
    public string Name { get; set; }

    public MyClass()
    {
        Name = "홍길동";
    }
} 

자동으로 구현된 속성을 초기화하기 위해서는 보통 생성자에서 초기화를 해주곤 했는데, 이제는 자동 속성 초기자를 사용하여 한줄로 초기화를 할 수 있게 되었다.

public string Name { get; set; } = "홍길동";

인덱스 초기자(Index initializers)

기존에는 컬렉션 객체를 초기화 하기 위해서는 중괄호로 묶고 쉼표로 구분하여 초기화할 수 있었다.

private Dictionary messages = new Dictionary
{
    { 404, "Page not Found"},
    { 302, "Page moved, but left a forwarding address."},
    { 500, "The web server can't come out to play today."}
};

그러면 인덱스 초기자는 무엇일까? 아래 예제를 살펴보도록 하자.

private Dictionary webErrors = new Dictionary
{
    [404] = "Page not Found",
    [302] = "Page moved, but left a forwarding address.",
    [500] = "The web server can't come out to play today."
};

인덱서를 통해 새로운 방법으로 사전 객체를 초기화하는 것을 볼 수 있다. 인덱서 구문이 키와 값을 확실하게 구별해주므로 기존의 방법보다 더 코드를 파악하기가 쉬워졌다.


nameof 연산자(nameof Operator)

nameof 연산자는 변수나 자료형, 멤버의 이름에 해당하는 문자열을 가져오는 데 사용된다. 아래 예제에서는 네임스페이스, 클래스, 메서드, 매개변수, 속성, 필드나 변수의 이름을 nameof 연산자로 가져오는 것을 보여준다.

using System;

public class Program
{
    private static DateTime Today = DateTime.Now;

    public string Name { get; set; }

    public static void Main(string[] args)
    {
        var localTime = DateTime.Now.ToLocalTime();

        Console.WriteLine(nameof(localTime));     // "localTime"
        Console.WriteLine(nameof(args));          // "args"
        Console.WriteLine(nameof(System.IO));     // "IO"
        Console.WriteLine(nameof(Main));          // "Main"
        Console.WriteLine(nameof(Program));       // "Program"
        Console.WriteLine(nameof(Program.Today)); // "Today"
        Console.WriteLine(nameof(Program.Name));  // "Name"
    }
}

그럼 nameof 연산자는 주로 어디에 사용되는 것일까? 매개변수의 유효성을 검사하거나, PropertyChanged 이벤트를 처리하거나, 로깅에 사용하거나, 열거자 이름을 가져오는 등 생각보다 다양한 곳에서 유용하게 사용할 수 있다. 하드코딩 시 개발자의 실수나, 프로그램 유지보수 도중 미처 수정하지 못한 경우 등과 같은 상황에서 유용하게 사용됨을 짐작해 볼 수 있다.

int p {  
   get { return this.p; }  
   set { this.p = value; PropertyChanged(this, new PropertyChangedEventArgs(nameof(this.p)); }
}


예외 필터(Exception filters)

예외 필터를 사용하면 catch 블록에서 조건을 지정해줄 수 있다. 이는 특정 조건을 만족할 때만 예외를 처리할 수 있도록 만들 수가 있다는 것이다.

WebClient wc = null;
try
{
   wc = new WebClient();
   var resultData = wc.DownloadString("http://google.com");
}
catch (WebException ex) when ((ex.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotFound)
{
   // WebException NotFound를 처리하기 위한 영역
}
catch (WebException ex) when ((ex.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.InternalServerError)
{
   // WebException InternalServerError를 처리하기 위한 영역
}
finally
{
   // 예외 발생 여부와 상관없이 호출
   wc?.Dispose();
}

  

catch/finally 블록 내 await 사용(await in catch/finally)

C# 5.0 부터 await 키워드가 추가되었지만, catch/finally 절에서는 해당할 수 없었다. C# 6.0부터는 이런 제한을 피해 구현하기 위해 별도의 처리 코드를 작성할 필요가 사라졌다. 주로 비동기적으로 예외를 기록하거나, 마지막으로 자원을 해제하는 등 catch/finally 절에서 비동기 작업을 수행할 필요가 있을 때 유용하게 사용될 수 있다.

try
{
    await OperationThatMayThrowAsync();
}
catch (ExpectedException ex)
{
    await MyLogger.LogAsync(ex);
}


식 본문 멤버(Expression-bodied members)

Expression-bodied 멤버는 코드를 더 간결하고 더 읽기 쉽도록 만들어 주는 기능이다. 속성/메서드의 본문(Body 블록)이 하나의 문장으로 끝나는 단순한 경우에 사용할 수 있다. 아래 예제와 같이 람다 화살표(=>)를 통해 읽기전용 속성이나 메서드를 간단하게 정의할 수 있다. (C# 7.0부터 get/set 접근자나 생성자, 소멸자, 인덱서로 기능이 확장됨)

public class Person
{
   public Person(string firstName, string lastName)
   {
      fname = firstName;
      lname = lastName;
   }

   private string fname;
   private string lname;
   
   // member => expression;
   public override string ToString() => $"{fname} {lname}".Trim(); // { return $"{fname} {lname}".Trim(); }과 동일
   public void DisplayName() => Console.WriteLine(ToString()); // { Console.WriteLine(ToString()); } 과 동일
}

위 예제에선 중괄호({})를 작성하지 않고 Expression-bodied 정의를 통해 단일 표현식을 가진 메서드를 간결하게 정의했다. 이번에는 Expression-bodied 멤버를 읽기전용 속성의 구현에 사용해보도록 하자.

public class Location
{
   private string locationName;
   
   public Location(string name)
   {
      locationName = name;
   }
   // PropertyType PropertyName => expression;
   public string Name => locationName;
}


널 조건 연산자(Null-conditional operators)

자세한 내용은 이곳을 참조하세요.


문자열 보간(String interpolation)

자세한 내용은 이곳을 참조하세요.