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)
12879정성태12/16/202113296오류 유형: 774. Windows Server 2022 + docker desktop 설치 시 WSL 2로 선택한 경우 "Failed to deploy distro docker-desktop to ..." 오류 발생
12878정성태12/15/20218345개발 환경 구성: 617. 윈도우 WSL 환경에서 같은 종류의 리눅스를 다중으로 설치하는 방법
12877정성태12/15/20217008스크립트: 36. 파이썬 - pymysql 기본 예제 코드
12876정성태12/14/20216832개발 환경 구성: 616. Custom Sources를 이용한 Azure Monitor Metric 만들기
12875정성태12/13/20216546스크립트: 35. python - time.sleep(...) 호출 시 hang이 걸리는 듯한 문제
12874정성태12/13/20216557오류 유형: 773. shell script 실행 시 "$'\r': command not found" 오류
12873정성태12/12/20217680오류 유형: 772. 리눅스 - PATH에 등록했는데도 "command not found"가 나온다면?
12872정성태12/12/20217464개발 환경 구성: 615. GoLang과 Python 빌드가 모두 가능한 docker 이미지 만들기
12871정성태12/12/20217581오류 유형: 771. docker: Error response from daemon: OCI runtime create failed
12870정성태12/9/20216164개발 환경 구성: 614. 파이썬 - PyPI 패키지 만들기 (4) package_data 옵션
12869정성태12/8/20218413개발 환경 구성: 613. git clone 실행 시 fingerprint 묻는 단계를 생략하는 방법
12868정성태12/7/20216984오류 유형: 770. twine 업로드 시 "HTTPError: 400 Bad Request ..." 오류 [1]
12867정성태12/7/20216675개발 환경 구성: 612. 파이썬 - PyPI 패키지 만들기 (3) entry_points 옵션
12866정성태12/7/202114046오류 유형: 769. "docker build ..." 시 "failed to solve with frontend dockerfile.v0: failed to read dockerfile ..." 오류
12865정성태12/6/20216740개발 환경 구성: 611. 파이썬 - PyPI 패키지 만들기 (2) long_description, cmdclass 옵션
12864정성태12/6/20215201Linux: 46. WSL 환경에서 find 명령을 사용해 파일을 찾는 방법
12863정성태12/4/20217119개발 환경 구성: 610. 파이썬 - PyPI 패키지 만들기
12862정성태12/3/20215870오류 유형: 768. Golang - 빌드 시 "cmd/go: unsupported GOOS/GOARCH pair linux /amd64" 오류
12861정성태12/3/20218099개발 환경 구성: 609. 파이썬 - "Windows embeddable package"로 개발 환경 구성하는 방법
12860정성태12/1/20216187오류 유형: 767. SQL Server - 127.0.0.1로 접속하는 경우 "Access is denied"가 발생한다면?
12859정성태12/1/202112374개발 환경 구성: 608. Hyper-V 가상 머신에 Console 모드로 로그인하는 방법
12858정성태11/30/20219637개발 환경 구성: 607. 로컬의 USB 장치를 원격 머신에 제공하는 방법 - usbip-win
12857정성태11/24/20217075개발 환경 구성: 606. WSL Ubuntu 20.04에서 파이썬을 위한 uwsgi 설치 방법
12856정성태11/23/20218881.NET Framework: 1121. C# - 동일한 IP:Port로 바인딩 가능한 서버 소켓 [2]
12855정성태11/13/20216231개발 환경 구성: 605. Azure App Service - Kudu SSH 환경에서 FTP를 이용한 파일 전송
12854정성태11/13/20217788개발 환경 구성: 604. Azure - 윈도우 VM에서 FTP 여는 방법
... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...