C# 7.3의 새로운 기능

출처

Overload 해결

버전 1.0의 C#에서 overload의 해결 규칙은 의문이 남는 디자인이었다.
어떤 상황에서 두 개 이상의 메소드가 후보가 되는데, 하나 밖에 사용할 수 없다. 우선 순위에 따라 잘못된 방법을 선택하거나 컴파일러가 일치하는 메소드를 찾지 못하거나 일치하지만 모호한 경우가 있다.

C# 7.3에서는 오버로드 해결 시 일부 확인이 되기 때문에 잘못된 일치에 의하여 컴파일 오류가 발생 하지 않게 되었다. 개선 된 overload 후보 제안의 개요:

  1. 인스턴스와 static 멤버가 모두 포함 되어 있는 경우 인스턴스 수신기 또는 컨텍스트 없이 호출된 경우 인스턴스 멤버를 무시하고, 인스턴스 수신기가 있고 호출된 경우는 static 멤버를 파기한다. 수신기가 없는 경우, static 문맥에 static 멤버만 포함, 이 이외의 때는 static와 인스턴스 멤버를 모두 포함한다. 수신기가 인스턴스 또는 타입이 모호한 경우 모두를 포함한다. 분명히 인스턴스 수신기를 사용할 수 없는 static 컨텍스트는 static 멤버 등의 멤버 본체가 정의 되지 않은 멤버, 필드 이니셜 라이저와 생성자 이니셜 라이저 등이 사용할 수 없는 장소가 포함된다.
  1. 형식 인수가 제약을 충족하지 않는 제네릭 메서드는 후보 세트로부터 삭제된다.
  1. 메서드 그룹의 변환은 후보 메서드가 delegate의 반환 형식과 일치하지 않는 경우 세트로부터 삭제된다.

제네릭 제약: enum, delegate, unmanaged

C# 2.0에서 제네릭이 도입된 이후, 개발자는 enum이 제네릭 형식으로 할 수 없는 것에 불만을 가지고 있었다.
이것은 궁극적으로 대처하여 enum 키워드를 제네릭으로 사용할 수 있게 되었다. 마찬가지로 키워드 delegate를 제네릭 제약에 이용할 수 있게 되었다.

이들은 반드시 정상적으로 작동하는 것은 아니다. 예를 들어 where T: enum 제약은 System.Enum의 서브 클래스로 사용하려는 것이지만, Foo 에서도 사용할 수 있다. 하지만 enum와 delegate 대부분의 시나리오를 커버하는 것이다.

Unmanaged 탑 제약은 “unmanaged” 키워드를 사용한 경우 “참조 타입이 아닌 중첩의 모든 수준에서 참조 형식의 필드가 포함 되어 있지 않은 타입”이어야 한다. 이것은 “모든 것이 비 관리 타입으로 재사용 가능한 루틴을 만든다” 필요가 있는 경우에 낮은 수준의 interop 코드에서 사용된다. 관리되지 않는 타입은 다음과 같다:

  • 원시적인 sbyte, byte, short, ushort, int 및 uint, long, ulong char, float, double, decimal, bool, IntPtr, UIntPtr
  • 모든 enum 타입
  • 포인터 타입
  • 상기만을 포함하는 사용자 정의 구조

숨겨진 필드의 특성

자동 구현 속성은 매우 편리하지만, 후방 필드에 속성을 적용 할 수 없기 때문에 이것을 볼 수 없는 등 몇 가지 제한이 있다. 일반적으로 문제가 되지 않지만, 직렬화를 처리 할 때 문제가 될 수 있다.

자동 구현된 속성 필드 대상 속성 제안은 이것을 곧바로 대처하고 있다. 자동 구현된 속성을 적용 할 때는 간단하게 field:수식자를 부여한다.

[Serializable]
public class Foo {

    [field: NonSerialized]
    public string MySecret { get; set; }
}

튜플 비교 (== 와 !=)

가장 중요한 변화는

만약 누군가가 비교 연산자를 구현한 ValueTuple 타입을 쓴다면 이전은 overload 해결로 선정했다. 그러나 새로운 튜플은 overload 해결 이전에 사용자 정의 비교에 의지하는 것이 아니라 튜플 비교가 처리된다.

이상적으로는 사용자 ValueTuple 타입은 C# 7.3 컴파일러와 같은 규칙을 따를 것이지만, 중첩된 튜플과 dynamic 타입의 처리에서 미묘한 차이가 발생한다.

이니셜 라이저 식변수

이것은 안티 기능처럼 보인다. 기능을 추가하는 것이 아니라 Microsoft는 식변수를 사용할 수 있는 장소의 제한을 제거했다.

ctor-initializer에서 식변수 선언(out 변수 선언과 선언 패턴)을 금지하는 제한을 제거했다. 선언된 변수는 생성자의 바디에 사용된다. 필드 또는 속성 이니셜 라이저의 식변수 선언(out 변수 선언과 선언 패턴)을 금지하는 제한을 제거했다. 선언된 변수는 이니셜 라이저 식 내에서 유효하다. lambda의 몸으로 변환 되는 쿼리식 변수 식선언(out 변수 선언과 선언 패턴)을 금지하는 제한을 제거했다. 선언된 변수는 쿼리 식에서 사용된다.

이러한 제한의 당초 이유는 단순히 ‘시간 부족’이라고 써여 있다. 아마도 이러한 제한에 의해 C# 7 이전 버전에서 필요했던 테스트 량이 감소된 것으로 생각된다.

스택에 할당된 배열

드물게 사용 되지 않지만, 중요한 C# 기능은 stackalloc 키워드로 스택에 할당된 배열이다. 이러면 힙에 할당 및 GC의 압박이 일어나지 않는 것으로, 보통의 배열은 성능이 개선 될 수 있다.

int* block = stackalloc int[3] { 1, 2, 3 };

스택에 할당된 배열을 사용하는 것은 위험 할 수있다. 스택에 해당 하는 포인트가 필요하게 되었을 때 unsafe 컨텍스트에서 안에서만 사용할 수 있다. CLR은 버퍼 오버런을 사용하여 이것을 줄이기 위해 노력하고 있다. 이 응용 프로그램은 “가능한 한 빨리 종료”하게 한다.

C# 7.3에서는 일반 배열과 마찬가지로 만들 때 배열을 초기화 할 수 있다. 이 제안은 세부 사항이 아니지만, Microsoft는 기능이 실행 되었을 때 신속하게 복사 할 수 있는 마스터 배열을 사전 초기화하는 것을 검토하고있다. 이론적으로는 요소마다 초기화 하는 것보다 빨라진다.

참고로 스택에 할당된 배열은 많은 소규모 단기 배열이 필요한 시나리오를 대상으로 하고있다. 큰 배열이나 깊은 재귀 함수에 사용하면 스택 영역을 초과 할 수 있기 때문에 사용해서는 안된다.

스택에 할당된 Span

스택에 할당된 배열의 안전한 대안은 스택에 할당된 span 이다.
포인터를 삭제하여 버퍼 오버런도 삭제할 수있다. 이렇게 하면 메소드를 unsafe로 하지 않아도 사용할 수 있다.

Span<int> block = stackalloc int[3] { 1, 2, 3 };

주의 사항으로 Span는 System.Memory NuGet 패키지가 필요하다.

재 할당 가능한 Ref 로컬

Ref 지역은 일반 지역 변수처럼 재 할당 가능하게 되었다.

다른 C# 7.3 제안은 csharplang GitHub 사이트를 참조하기 바란다.


이 글은 2019-02-11에 작성되었습니다.