C# 7.0의 새로운 기능

이번 .NET Core 출시 이례로 C# ~6.0 기반의 모든 것들을 이제 .NET Core에 집중할 예정이라고 한다.
그리고, Roslyn 컴파일러 덕분에 C# 6.0 문법이 C# 5.0 인 닷넷 환경에서도 지원할 수 있도록 어셈블리를 제공하고 있으니,
아마 구버전 문법 환경에서도 사용할 수 있도록 지원되지 않을까 예측을 할려다 말았다. 굳이…

자, 본문으로 들어가서, 차기 닷넷코어에 탑재될 C# 7.0 언어의 새로운 기능을 Araboza.

Tuple

자바에서 걍 값만 쳐넣는 클래스를 POJO라고 한다. 그리고 닷넷에서는 POCO라고 한다.
대충 이런 식이다.

class PropertyBag
{
    public int Id {get; set;}
    public string Name {get; set;}
}
var myObj = new PropertyBag { Id = 1, Name = "test};

C# 3.0부터 지원해서 여러분들도 자주 써왔을 것이다.
닷넷에서는 이런 놈들을 지칭하는 용어가 튜플이다.

out 인자

잘 쓰지는 않지만 out 인자를 통해 메소드에서 여러 인자를 받아낼 수 있다.

public void GetLatLng(string address, out double lat, out double lng) { ... }

double lat, lng;
GetLatLng(myValues, out lat, out lng);
Console.WriteLine($"Lat: {lat}, Long: {lng}"); 

하지만 가장 큰 문제점이 있다면 바로 async 메소드에 쓸 수가 없다는 것.
그리고 코딩하기 참 불편하다. 패턴 참 개같아 보인다.

Tuple<> 클래스

C# 3.0부터 Tuple 이라는 클래스가 생겼는데, POCO 만들기 귀찮을 때 유용하기도 하다.

public Tuple<int, int> GetLatLng(string address) { ... }

var latLng = GetLatLng("some address");
Console.WriteLine($"Lat: {latLng.Item1}, Long: {latLng.Item2}");

근데… 알다시피 프로퍼티 이름 참… 맘대로 못짓는다. 어쩔 수 없다. 제공 클래스의 한계니까.

POCO

가장 많이 쓰는 패턴이다. Value Object 답게 읽거나 쓸 수 있다.

struct LatLng{ public double Lat; public double Lng;}
public LatLng GetLatLng(string address) { ... }

var ll= GetLatLng("some address");
Console.WriteLine($"Lat: {ll.Lat}, Long: {ll.Lng}"); 

근데 코딩 길이 참 길다. 원래 그렇지 뭐.

Tuple 반환 타입

자, 이제 C# 7.0의 튜플 형식에 주목해보자.
먼저, 2개 이상의 원하는 이름으로 반환 타입을 만들 수 있다.

public (string FirstName, string LastName) GetNames(string! fullName) //형식 뒤에 느낌표가 뭔지는 다음에 설명.
{
  string[] names = fullName.Split(" ", 2);
  return (names[0], names[1]);
}

var name = GetNames("john doe"); 
Console.WriteLine($"Name: {name.FirstName} {name.LastName}");

자, 이렇게 하면 out 변수와 같은 효과를 내면서, 심지어 async 메소드에 대응이 가능하다고 한다. 오예.
원리야 Tuple에 네 변수를 대입하는 방식이니 편하게 써주자.

당연히 인라인도 가능하다.

var ll = new (double lat, double lng) { lat = 0, lng = 0 };

이렇게 하면 li 변수에 당신이 정한 tuple 멤버를 속성으로 사용하여 다룰 수 있다.

레코드 형식

별 거 없다. POCO 클래스 작성하기 간결해지는 효과가 있다. 예를 들면,

class MyPoint
{
    int _x;
    int _y;
    int _z;
    MyPoint(int x, int y, int z){
        this._x = x;
        this._y = y;
        this._z = z;
    }
    public int X {get{ return this._x;}}
    public int Y {get{ return this._y;}}
    public int Z {get{ return this._z;}}
}

C# 7.0부터 아래처럼 짜면 위와 비슷한 클래스가 만들어진다.

class Point(int X, int Y, int Z);

이 때, 알아야 할 점이 있다.

  • 레코드 형식도 상속 “된다”.
  • 레코드 형식은 IEquatable<> 인터페이스에 상속된다. 이 덕분에 같거나 틀린 연산자로 비교 시 레퍼런스 비교가 아닌 멤버 값 비교를 통해 비교가 가능하다.
  • ToString() 메소드는 자동으로 오버라이드 되어 레코드 각 멤버와 값을 나열하게 된다.

패턴 매칭

C# 7.0부터 나온 재밌는 기능이다. 이 기능 때문에 switch 문이 재밌어질 것이다.
먼저 예제를 보자.

class Geometry();
class Triangle(int Width, int Height, int Base) : Geometry;
class Rectangle(int Width, int Height) : Geometry;
class Square(int width) : Geometry;

Geometry g = new Square(5);
switch (g)
{
    case Triangle(int Width, int Height, int Base):
        WriteLine($"{Width} {Height} {Base}");
        break;
    case Rectangle(int Width, int Height):
        WriteLine($"{Width} {Height}");
        break;
    case Square(int Width):
        WriteLine($"{Width}");
        break;
    default:
        WriteLine("<other>");
        break;
}

간단히 얘기하자면, 첫번째로 얘기한 튜플 형식의 산출물인 셈인데,
switch 문을 통해 각 상속 된 클래스와 멤버가 맞는지 비교하여 해당될 때 문을 실행할 수 있다.
예를 들면, 사용자가 있고, 사용자가 개인 사용자, 단체 사용자가 있다고 할 때,
switch 문으로 개인 사용자와 단체 사용자에 따라 다른 형식으로 문을 작성한다고 보면 될 것이다.

Non-nullable 참조 형식

참조 형식은 class로 시작하는 것들이다. 자바야 내장 값 형식 말고 죄다 참조이니 뭐.
어쨌든, 참조 형식은 기본적으로 null 이 가능하다. 이제 불가능하도록 설정할 수 있다.
이런 형식을 제안한 사람이 있는데 이 링크로 들어가면 성지순례할 수 있다.무려 5년 전에 제안한 기능이다.

값(struct) 형식은 기본적으로 null 을 가질 수 없다. 반드시 기본값을 가지게 되어 있다.
하지만 Nullable<> 형식이 생겨 값 형식 뒤에 ? 만 붙이면 null을 가질 수 있다.

int a = 1; // 일반 값 형식
int? b = null; //null을 가질 수 잇는 값 형식

이 문법 덕에 DB나 타언어 연동 시 유연하게 대응이 가능할 것이다.
여기서 주의할 점은 null을 가질 수 있을 뿐 절대 참조 형식이 아니라는 점.

자, 이번엔 참조 형식이다. 참조 형식 중 대표적인 string 형식이 있는데,
non-null 처리 하려면 별도의 처리를 해야 한다. 하지만 이제 아래처럼 하면 된다.

string a = null;
string! b = null;

형식 뒤에 느낌표(!)를 붙이면 null이 불가능한 참조 변수를 만들 수 있다.
목적은 컴파일 타임에서 null이 불가능한 참조 변수를 만드는 데 목적이 있기 때문에
위처럼 코딩하면 컴파일 오류가 난다. 당연히 null 이 아닌 값이나 초기화를 해 줘야 한다.
string 뿐만 아니라 당신이 사용하는 클래스에도 당연히 적용 가능하다.

제네릭도 적용 가능하다.

//이렇게 하면 Dictionary 형식만 non-nullable.
Dictionary<string, List<MyClass>>! myDict;

//이런 식으로 원하는 형식 뒤에 non-nullable 참조를 정의할 수 있다.
Dictionary<string!, List<MyClass!>!>! myDict;

//제네릭과 제네렉 하위 모든 형식에 non-nullable 참조를 원할 경우 형식과 제네릭 사이에 ! 붙이면 된다.
Dictionary!<string, List<MyClass>> myDict;

지역 함수

클래스 멤버인 메소드와는 달리 블록 내에 잠깐 처리하고자 할 경우 사용하는 방법이 나같은 경우 람다식이다. 그렇게 가능하다.
하지만 이런 형식도 한계가 있는데,

  • 제네릭 사용 불가
  • refout 사용 불가능
  • 가변 인자 사용 불가

이를 간결하게 대비할 수 있는 지역 함수 기능을 제공한다.
람다식에서 못한 한계를 여기서 해결할 수 있다.

public int Calculate(int someInput)
{
    int Factorial(int i)
    {
        if (i <= 1)
            return 1;
        return i * Factorial(i - 1);
    }
    var input = someInput + ... // Other calcs

    return Factorial(input);
}

Factorial 이란 함수는 Calculate 메소드 문 내에서만 사용할 수 있다는 특징이 있다.
그 외에는 메소드처럼 정의하고 사용하면 된다.

불멸 형식(Immutable Types)

C#이나 자바나 대표적인 불멸 형식은 string 이다.
불멸 형식은 아래와 같은 장점이 있다.

  • 스레드로부터 안전하다.
  • 코드의 목적이 명확하다.
  • 병렬 처리가 쉽다.
  • 참조가 캐쉬되지 않으며 바뀌지도 않는다.

자, 사용법을 알아보자.
일반적인 POCO 클래스이다.

public class Point
{
    public Point(int x, int y)
    {
        x = x;
        Y = y;
    }

    public int X { get; }
    public int Y { get; }
}

초기화 시 좌표값을 받고 써먹는 클래스이다.
하지만 누군가 setter 넣고 중간에 값을 바꿔치기 하면 원치 않는 결과가 나올 텐데,
class 앞에 immutable 키워드만 넣으면 만사 OK.

public immutable class Point
{
    public Point(int x, int y)
    {
        x = x;
        Y = y;
    }

    public int X { get; }
    public int Y { get; }
}

이렇게 하면, string 처럼 중간에 값이 변조될 가능성이 없어지기 때문에, 인스턴스를 사용하는 동안 참조를 보장받을 수 있다.
즉, 메소드가 중간에 변조할 수 없다는 뜻이다. 처음부터 끝까지 초기화한 형식 그대로 갖다 쓸 수 있다는 것.
게다가 이런 형식에 대비해 유용한 문법이 생겼는데, 기존 인스턴스로부터 값을 바꿔 새 인스턴스로 만들 수 있는 문법이 생겼다.

var a = new Point(2, 5);
var b = a with {X = 1}; // {X = 1, Y = 5}

존나좋군.

언제 이런 문법으로 코딩 가능?

.NET 로드맵에 의하면, 올해 가을에 1.1이 나오는데, 안타깝게도 아직은 이 스펙을 못쓴다. 하지만 아직 이게 다가 아니라고 한다.
이 스펙을 사용할 수 있는 시기가 MSDN에 의하면, 차기 비주얼 스튜디오부터 이 스펙을 사용 가능하다고 알려져 있다. 문제는 비주얼 스튜디오가 언제 나올 지 모른다. 내년에 나오겠지 뭐.
Github Roslyn work list of features 에 소개된 기능 외에 고려하고 있는 기능을 볼 수 있으며, 심지어 참여도 가능하니,
관심이 있으면 주저 말고 영어로 의견을 내놓으면 된다.

근데… 자바는 아직도 1.x인데 2.x 버전이 지구 멸망하기 전까지 나올런지 모르겠다.
여담으로, WCF는 닷넷 코어에 채용이 됐지만, WPF는 계획이 없다고 한다. 그걸 기다리느니 차라리 Electron에 닷넷 붙이는 게 크로스 플랫폼에 효율적일 듯 하다. 아님 자마린으로 모바일 개발 하던가.

참고자료

미안. 죄다 영어다. 한글 자료는 아무리 찾아봐도 없더라. 이 글을 쓴 이유이기도 하다.

C# 7: New Features
New features in C# 7, part 2
Essential .NET – Designing C# 7

composite / 2016년 7월 19일 / Piss Development / 1 Comment

[C#] 명쾌한 abstract 메소드와 virtual 메소드 차이점

왠지 모르게 다들 어렵게 설명한다.
데브피아만 가도 “abstract는 없으면 인스턴스 생성 안되고 virtual 은 없어도 인스턴스 생성된다…”
한국 종특인 돌려말하기인지 모르겠지만, 초보들에게는 더럽게 이해 안되는 답변이다.
심지어 그런거 모르냐, 책을 읽어라, 알게된다. 이지랄까지 하고 있으니…
선배 개발자들 맞는지 의문 투성이다 정말. 먹고살기 빠듯한 건 아는데 그렇게 가르쳐주기 귀찮았냐 하는 생각이 든다.
내가 후배 개발자를 위해 간단명료하게 설명한다.

자. 네가 얻은 새로운 집에 시멘트만 있는 방 내부를 꾸미게 됐어.
이게 abstract class 다.

벽지는 붙여야지? 안그럼 방이냐? 폐가지.
그리고 장판 깔아야지 안그럼 맨발로 방 못들어가잖아.
그래서 벽지와 장판 붙이는 기능을 가진 메소드를 abstract로 선언한다.

좋아. 벽지는 일단 붙였어. 그럼 벽지에 데코를 붙이는 생각도 했을거야.
예를 들면, 벽에 뭐 매달거나, 야광 스티커를 붙이거나 할거야.
근데 이건 안해도 지장 없잖아.
그래서 벽지 데코를 붙이는 메소드를 virtual로 선언한다.

이해했지?

public abstract class 추상인테리어{
    public abstract 벽지 벽지칠하기();
    public abstract 장판 장판붙이기();
    public virtual 데코레이션 데코붙이기(){
        return null; // 기본 데코 안붙일거다.
    }
}

public sealed class 철수인테리어 : 추상인테리어{
    public override 벽지 벽지칠하기(){
        var 벽지칠 = new 벽지();
        var 벽지색 = 벽지색깔.파란색;
        //벽지 칠하는 프로세스 구현
        return 벽지칠;
    }
    public override 장판 장판붙이기(){
        var 장판깔기 = new 장판(장판.흔한거);
        //장판 까는 프로세스 구현
        return 장판깔기;
    }
}

public class 영희인테리어 : 추상인테리어{
    public override 벽지 벽지칠하기(){
        var 벽지칠 = new 벽지();
        var 벽지색 = 벽지색깔.분홍색;
        벽지칠.패턴 = 벽지패턴.꽃무늬;
        //벽지 칠하는 프로세스 구현
        return 벽지칠;
    }
    public override 장판 장판붙이기(){
        var 장판깔기 = new 장판(장판.예쁜거);
        //장판 까는 프로세스 구현
        return 장판깔기;
    }
    public override 데코레이션 데코붙이기(){
        var 데코 = new 데코레이션();
        데코.종류 = 스티커.야광(색깔.초록색);
        //데코 붙이는 프로세스 구현
        return 데코.다붙였다();
    }
}

존나 흔한 이름으로 클래스 붙였다.
끗.

composite / 2015년 8월 21일 / Piss Development / 3 Comments