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)
13179정성태12/2/20224353Windows: 216. Windows 11 - 22H2 업데이트 이후 Terminal 대신 cmd 창이 뜨는 경우
13178정성태12/1/20224854Windows: 215. Win32 API 금지된 함수 - IsBadXxxPtr 유의 함수들이 안전하지 않은 이유파일 다운로드1
13177정성태11/30/20225579오류 유형: 829. uwsgi 설치 시 fatal error: Python.h: No such file or directory
13176정성태11/29/20224510오류 유형: 828. gunicorn - ModuleNotFoundError: No module named 'flask'
13175정성태11/29/20226126오류 유형: 827. Python - ImportError: cannot import name 'html5lib' from 'pip._vendor'
13174정성태11/28/20224701.NET Framework: 2073. C# - VMMap처럼 스택 메모리의 reserve/guard/commit 상태 출력파일 다운로드1
13173정성태11/27/20225388.NET Framework: 2072. 닷넷 응용 프로그램의 스레드 스택 크기 변경
13172정성태11/25/20225200.NET Framework: 2071. 닷넷에서 ESP/RSP 레지스터 값을 구하는 방법파일 다운로드1
13171정성태11/25/20224812Windows: 214. 윈도우 - 스레드 스택의 "red zone"
13170정성태11/24/20225117Windows: 213. 윈도우 - 싱글 스레드는 컨텍스트 스위칭이 없을까요?
13169정성태11/23/20225706Windows: 212. 윈도우의 Protected Process (Light) 보안 [1]파일 다운로드2
13168정성태11/22/20224993제니퍼 .NET: 31. 제니퍼 닷넷 적용 사례 (9) - DB 서비스에 부하가 걸렸다?!
13167정성태11/21/20225030.NET Framework: 2070. .NET 7 - Console.ReadKey와 리눅스의 터미널 타입
13166정성태11/20/20224754개발 환경 구성: 651. Windows 사용자 경험으로 WSL 환경에 dotnet 런타임/SDK 설치 방법
13165정성태11/18/20224662개발 환경 구성: 650. Azure - "scm" 프로세스와 엮인 서비스 모음
13164정성태11/18/20225560개발 환경 구성: 649. Azure - 비주얼 스튜디오를 이용한 AppService 원격 디버그 방법
13163정성태11/17/20225499개발 환경 구성: 648. 비주얼 스튜디오에서 안드로이드 기기 인식하는 방법
13162정성태11/15/20226579.NET Framework: 2069. .NET 7 - AOT(ahead-of-time) 컴파일
13161정성태11/14/20225797.NET Framework: 2068. C# - PublishSingleFile로 배포한 이미지의 역어셈블 가능 여부 (난독화 필요성) [4]
13160정성태11/11/20225747.NET Framework: 2067. C# - PublishSingleFile 적용 시 native/managed 모듈 통합 옵션
13159정성태11/10/20228952.NET Framework: 2066. C# - PublishSingleFile과 관련된 옵션 [3]
13158정성태11/9/20225225오류 유형: 826. Workload definition 'wasm-tools' in manifest 'microsoft.net.workload.mono.toolchain' [...] conflicts with manifest 'microsoft.net.workload.mono.toolchain.net7'
13157정성태11/8/20225884.NET Framework: 2065. C# - Mutex의 비동기 버전파일 다운로드1
13156정성태11/7/20226790.NET Framework: 2064. C# - Mutex와 Semaphore/SemaphoreSlim 차이점파일 다운로드1
13155정성태11/4/20226289디버깅 기술: 183. TCP 동시 접속 (연결이 아닌) 시도를 1개로 제한한 서버
13154정성태11/3/20225771.NET Framework: 2063. .NET 5+부터 지원되는 GC.GetGCMemoryInfo파일 다운로드1
... 16  17  [18]  19  20  21  22  23  24  25  26  27  28  29  30  ...