Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 17개 있습니다.)

C# 9.0 - (10) 대상으로 형식화된 조건식(Target-typed conditional expressions)

C# 9.0 - (1) 대상으로 형식화된 new 식(Target-typed new expressions)
; https://www.sysnet.pe.kr/2/0/12363

C# 9.0 - (2) localsinit 플래그 내보내기 무시(Suppress emitting localsinit flag)
; https://www.sysnet.pe.kr/2/0/12364

C# 9.0 - (3) 람다 메서드의 매개 변수 무시(Lambda discard parameters)
; https://www.sysnet.pe.kr/2/0/12365

C# 9.0 - (4) 원시 크기 정수(Native ints)
; https://www.sysnet.pe.kr/2/0/12366

C# 9.0 - (5) 로컬 함수에 특성 지정 가능(Attributes on local functions)
; https://www.sysnet.pe.kr/2/0/12372

C# 9.0 - (6) 함수 포인터(Function pointers)
; https://www.sysnet.pe.kr/2/0/12374

C# 9.0 - (7) 패턴 일치 개선 사항(Pattern matching enhancements)
; https://www.sysnet.pe.kr/2/0/12383

C# 9.0 - (8) 정적 익명 함수 (static anonymous functions)
; https://www.sysnet.pe.kr/2/0/12389

C# 9.0 - (9) 레코드 (Records)
; https://www.sysnet.pe.kr/2/0/12392

C# 9.0 - (10) 대상으로 형식화된 조건식(Target-typed conditional expressions)
; https://www.sysnet.pe.kr/2/0/12399

C# 9.0 - (11) 공변 반환 형식(Covariant return types)
; https://www.sysnet.pe.kr/2/0/12402

C# 9.0 - (12) foreach 루프에 대한 GetEnumerator 확장 메서드 지원(Extension GetEnumerator)
; https://www.sysnet.pe.kr/2/0/12403

C# 9.0 - (13) 모듈 이니셜라이저(Module initializers)
; https://www.sysnet.pe.kr/2/0/12404

C# 9.0 - (14) 부분 메서드에 대한 새로운 기능(New features for partial methods)
; https://www.sysnet.pe.kr/2/0/12405

C# 9.0 - (15) 최상위 문(Top-level statements)
; https://www.sysnet.pe.kr/2/0/12406

C# 9.0 - (16) 제약 조건이 없는 형식 매개변수 주석(Unconstrained type parameter annotations)
; https://www.sysnet.pe.kr/2/0/12423




(이번 실습은 - 오늘 기준 16.7.7의 Visual Studio 2019에서 지원하지 않으므로 Visual Studio Preview 버전으로 실습해야 합니다.)

일반적으로 조건 연산자는 2항과 3항의 식이 어느 한 쪽으로든 암시적 형 변환이 가능한 유형이어야 합니다. 만약 그것이 불가능한 경우에는 컴파일 오류가 발생하는데요, 예를 들어 다음의 예제가 그런 경우입니다.

// https://anthonygiretti.com/2020/06/21/introducing-c-9-improved-target-typing/
class Program
{
    static void Main(string[] args)
    {
        Book aBook = new Book();
        Headset headset = new Headset();

        {
            // C# 8.0 이전: 컴파일 오류, 9.0부터 가능
            // Error CS0173 Type of conditional expression cannot be determined because there is no implicit conversion between 'Book' and 'Headset'
            Product prd = aBook != null ? aBook : headset;
        }

        {
            Product prd = null;

            if (aBook != null) prd = aBook;
            else prd = headset;
        }
    }
}

public class Product
{
}

public class Book : Product
{
}

public class Headset : Product
{
}

왜냐하면, 개별적으로는 Book과 Headset 타입이 Product로 암시적 형변환은 가능하지만, Book과 Headset 중 어느 한 쪽으로의 형변환은 가능하지 않기 때문입니다. 만약 이 상태에서 컴파일을 정상적으로 하고 싶다면 2가지 방법이 있습니다. 하나는 Book이나 Headset 타입 중에서 암시적 형변환 연산자를 재정의하는 것입니다. 예를 들어, Book에 Headset으로의 암시적 형변환을 가능하게 만들면,

public class Book : Product
{
    public static implicit operator Headset(Book instance) => new Headset();
}

이제 조건 연산자는, 2항과 3항 식의 타입이 Headset으로 일치하게 되고, Headset 또한 (당연히 기반 타입의) Product 타입으로 암시적 형변환이 가능하므로 아무런 오류 없이 컴파일을 합니다.

혹은, 위와 같이 (억지로) 암시적 형변환을 할 수 없는 상황이라면 단순히 cast 연산자를 직접 써도 됩니다.

Product prd = aBook != null ? (Product)aBook : headset;

그럼 2항의 식이 Product 타입으로 평가되고, 3항의 headset 또한 2항의 Product 타입으로 암시적 형변환이 가능하게 바뀝니다.

이런 불편함을, C# 9.0부터 새롭게 도입한 조건 식의 암시적 형변환 규칙으로 인해 2항과 3항의 타입이 대상 타입으로 암시적 형변환이 가능하다면 C# 컴파일러가 대상 타입을 맞춰주기(target-typed)로 한 것입니다.

이 규칙은 null 리터럴이 사용되는 경우에도 적용됩니다. 가령 다음의 코드는 C# 8.0 이전에는 오류가 발생했지만,

// C# 8.0 이전: 컴파일 오류
// Error CS0173 Type of conditional expression cannot be determined because there is no implicit conversion between 'int' and ''
int? result = aBook != null ? 0 : null;

9.0 이후부터는 2항의 0과 3항의 null이 int? 타입으로 대입이 가능하므로 컴파일러가 알아서 target-typed 처리를 해줍니다.




이와 함께 한 가지 더 변화가 생겼는데요. 예를 들어 다음의 조건 연산자가 int 없이 short와 long만 인자로 받는 메서드가 있는 상황에서,

// C# - 조건 연산자(?:)를 사용하는 경우 달라지는 메서드 선택 사례
// https://www.sysnet.pe.kr/2/0/12397

using System;

class Program
{
    static void Main(string[] args)
    {
        M(args.Length == 0 ? 1 : 2);
    }

    static void M(short n) { Console.WriteLine("Short"); }
    static void M(long n) { Console.WriteLine("Long"); }
}

/* 출력 결과
Long
*/

M(long) 버전이 선택되지만 비슷한 수식으로 C# 8.0 이후 나온 switch 식의 경우엔,

M((args.Length == 1) switch { true => 1, false => 2 }); // 출력 결과: Short

short로 평가되어 그 결과가 서로 다르다는 문제가 있습니다.

왜냐하면, switch 식의 경우 모든 case에 해당하는 결과를 최종 반환 타입의 평가에 사용하기 때문입니다. 즉, 타입 추론 기능이 좋아진 것입니다. (동일한 타입 추론 규칙을 조건 연산자에도 적용할 수 있었겠지만, 그런 경우 하위 호환성이 없어지므로 기존 프로그램을 다시 빌드하면 동작이 달라지는 문제가 발생하므로 그대로 남긴 듯합니다.)

그런데, C# 9.0부터는 이런 식의 불일치를 최대한 막기 위한 제약을 추가했습니다. 예를 들어, 다음과 같은 코드는 C# 8.0 까지는,

using System;

class Program
{
    static void Main(string[] args)
    {
        M(args.Length == 0 ? 1 : 2, 1);
    }

    static void M(short n1, short n2) { Console.WriteLine("Short"); }
    static void M(long n1, long n2) { Console.WriteLine("Long"); }
}

/* 출력 결과
Long
*/

정상적으로 빌드가 되었습니다. 첫 번째 인자의 "args.Length == 0 ? 1 : 2" 조건식은 int에서 long으로 암시적 형변환이 이뤄지고, 두 번째 인자는 암시적 형변환이 이뤄진 short로 일치하는 타입의 메서드가 있음에도 long 형으로 다시 암시적 형변환이 일어나 결국 M(long, long) 버전의 메서드가 선택된 것입니다.

이런 평가는, 인자가 하나만 있을 때와 비교해 타입 평가가 점점 더 틀어지고 있다고 볼 수 있습니다. 왜냐하면 두 번째 인자와 정확히 일치하는 타입이 있는데도 불구하고 첫 번째 인자가 C# 1.0 때부터의 호환을 지키기 위해 int -> long으로 형변환을 발생시켜 맞춰 나가고 있기 때문입니다. 타입 추론이 강화되면서 점차 short로도 판정할 수 있지만 차마 하지 못했던 것을, 즉, 바로 잡지는 못할망정 점점 더 어긋나는 것은 막기 위해 제약을 두기 시작한 것입니다. 물론, 그렇다고 그런 경우에만 조건 연산자의 결과를 short로 판정하는 것은 또 규칙에 어긋나기 때문에 아예 모호하다는 컴파일 오류를 발생하기로 결정한 듯합니다.

// C# 9.0 이후 - Error CS0121 The call is ambiguous between the following methods or properties: 'Program.M(short, short)' and 'Program.M(long, long)'
M(args.Length == 1 ? 1 : 2, 1);

그래서, 기존 소스를 빌드하는 중에 위와 같은 오류가 발생한다면 개발자는 명시적으로 형변환을 추가해야 합니다.

M((short)(args.Length == 1 ? 1 : 2, 1)); // M(short, short)
M((long)(args.Length == 1 ? 1 : 2, 1));  // M(long, long)

참고로, 문서에 보면 이런 변화에 대해 "This breaking change seems less serious because it does not silently change the behavior of an existing program."라고 언급합니다.

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 11/22/2020]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 16  17  18  19  20  21  22  23  [24]  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13021정성태3/31/20226679Windows: 201. Windows - INF 파일을 이용한 장치 제거 방법
13020정성태3/30/20226423.NET Framework: 1187. RDP 접속 시 WPF UserControl의 Unloaded 이벤트 발생파일 다운로드1
13019정성태3/30/20226407.NET Framework: 1186. Win32 Message를 Code로부터 메시지 이름 자체를 구하고 싶다면?파일 다운로드1
13018정성태3/29/20226933.NET Framework: 1185. C# - Unsafe.AsPointer가 반환한 포인터는 pinning 상태일까요? [5]
13017정성태3/28/20226777.NET Framework: 1184. C# - GC Heap에 위치한 참조 개체의 주소를 알아내는 방법 - 두 번째 이야기 [3]
13016정성태3/27/20227626.NET Framework: 1183. C# 11에 추가된 ref 필드의 (우회) 구현 방법파일 다운로드1
13015정성태3/26/20228966.NET Framework: 1182. C# 11 - ref struct에 ref 필드를 허용 [1]
13014정성태3/23/20227576VC++: 155. CComPtr/CComQIPtr과 Conformance mode 옵션의 충돌 [1]
13013정성태3/22/20225896개발 환경 구성: 641. WSL 우분투 인스턴스에 파이썬 2.7 개발 환경 구성하는 방법
13012정성태3/21/20225261오류 유형: 803. C# - Local '...' or its members cannot have their address taken and be used inside an anonymous method or lambda expression
13011정성태3/21/20226724오류 유형: 802. 윈도우 운영체제에서 웹캠 카메라 인식이 안 되는 경우
13010정성태3/21/20225683오류 유형: 801. Oracle.ManagedDataAccess.Core - GetTypes 호출 시 "Could not load file or assembly 'System.DirectoryServices.Protocols...'" 오류
13009정성태3/20/20227229개발 환경 구성: 640. docker - ibmcom/db2 컨테이너 실행
13008정성태3/19/20226518VS.NET IDE: 176. 비주얼 스튜디오 - 솔루션 탐색기에서 프로젝트를 선택할 때 csproj 파일이 열리지 않도록 만드는 방법
13007정성태3/18/20226151.NET Framework: 1181. C# - Oracle.ManagedDataAccess의 Pool 및 그것의 연결 개체 수를 알아내는 방법파일 다운로드1
13006정성태3/17/20227183.NET Framework: 1180. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 remuxing.c 예제 포팅
13005정성태3/17/20226064오류 유형: 800. C# - System.InvalidOperationException: Late bound operations cannot be performed on fields with types for which Type.ContainsGenericParameters is true.
13004정성태3/16/20226090디버깅 기술: 182. windbg - 닷넷 메모리 덤프에서 AppDomain에 걸친 정적(static) 필드 값을 조사하는 방법
13003정성태3/15/20226235.NET Framework: 1179. C# - (.NET Framework를 위한) Oracle.ManagedDataAccess 패키지의 성능 카운터 설정 방법
13002정성태3/14/20226984.NET Framework: 1178. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 http_multiclient.c 예제 포팅
13001정성태3/13/20227388.NET Framework: 1177. C# - 닷넷에서 허용하는 메서드의 매개변수와 호출 인자의 최대 수
13000정성태3/12/20226950.NET Framework: 1176. C# - Oracle.ManagedDataAccess.Core의 성능 카운터 설정 방법
12999정성태3/10/20226445.NET Framework: 1175. Visual Studio - 프로젝트 또는 솔루션의 Clean 작업 시 응용 프로그램에서 생성한 파일을 함께 삭제파일 다운로드1
12998정성태3/10/20226062.NET Framework: 1174. C# - ELEMENT_TYPE_FNPTR 유형의 사용 예
12997정성태3/10/202210443오류 유형: 799. Oracle.ManagedDataAccess - "ORA-01882: timezone region not found" 오류가 발생하는 이유
12996정성태3/9/202215603VS.NET IDE: 175. Visual Studio - 인텔리센스에서 오버로드 메서드를 키보드로 선택하는 방법
... 16  17  18  19  20  21  22  23  [24]  25  26  27  28  29  30  ...