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

비밀번호

댓글 작성자
 




1  2  3  4  [5]  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13501정성태12/25/20232087개발 환경 구성: 700. WSL + uwsgi - IPv6로 바인딩하는 방법
13500정성태12/24/20232178디버깅 기술: 194. Windbg - x64 가상 주소를 물리 주소로 변환
13498정성태12/23/20232824닷넷: 2186. 한국투자증권 KIS Developers OpenAPI의 C# 래퍼 버전 - eFriendOpenAPI NuGet 패키지
13497정성태12/22/20232294오류 유형: 885. Visual Studiio - error : Could not connect to the remote system. Please verify your connection settings, and that your machine is on the network and reachable.
13496정성태12/21/20232310Linux: 66. 리눅스 - 실행 중인 프로세스 내부의 환경변수 설정을 구하는 방법 (gdb)
13495정성태12/20/20232318Linux: 65. clang++로 공유 라이브러리의 -static 옵션 빌드가 가능할까요?
13494정성태12/20/20232501Linux: 64. Linux 응용 프로그램의 (C++) so 의존성 줄이기(ReleaseMinDependency) - 두 번째 이야기
13493정성태12/19/20232559닷넷: 2185. C# - object를 QueryString으로 직렬화하는 방법
13492정성태12/19/20232262개발 환경 구성: 699. WSL에 nopCommerce 예제 구성
13491정성태12/19/20232232Linux: 63. 리눅스 - 다중 그룹 또는 사용자를 리소스에 권한 부여
13490정성태12/19/20232345개발 환경 구성: 698. Golang - GLIBC 의존을 없애는 정적 빌드 방법
13489정성태12/19/20232133개발 환경 구성: 697. GoLand에서 ldflags 지정 방법
13488정성태12/18/20232067오류 유형: 884. HTTP 500.0 - 명령행에서 실행한 ASP.NET Core 응용 프로그램을 실행하는 방법
13487정성태12/16/20232381개발 환경 구성: 696. C# - 리눅스용 AOT 빌드를 docker에서 수행 [1]
13486정성태12/15/20232195개발 환경 구성: 695. Nuget config 파일에 값 설정/삭제 방법
13485정성태12/15/20232088오류 유형: 883. dotnet build/restore - error : Root element is missing
13484정성태12/14/20232162개발 환경 구성: 694. Windows 디렉터리 경로를 WSL의 /mnt 포맷으로 구하는 방법
13483정성태12/14/20232298닷넷: 2184. C# - 하나의 resource 파일을 여러 프로그램에서 (AOT 시에도) 사용하는 방법파일 다운로드1
13482정성태12/13/20232821닷넷: 2183. C# - eFriend Expert OCX 예제를 .NET Core/5+ Console App에서 사용하는 방법 [2]파일 다운로드1
13481정성태12/13/20232267개발 환경 구성: 693. msbuild - .NET Core/5+ 프로젝트에서 resgen을 이용한 리소스 파일 생성 방법파일 다운로드1
13480정성태12/12/20232608개발 환경 구성: 692. Windows WSL 2 + Chrome 웹 브라우저 설치
13479정성태12/11/20232302개발 환경 구성: 691. WSL 2 (Ubuntu) + nginx 환경 설정
13477정성태12/8/20232483닷넷: 2182. C# - .NET 7부터 추가된 Int128, UInt128 [1]파일 다운로드1
13476정성태12/8/20232207닷넷: 2181. C# - .NET 8 JsonStringEnumConverter의 AOT를 위한 개선파일 다운로드1
13475정성태12/7/20232279닷넷: 2180. .NET 8 - 함수 포인터에 대한 Reflection 정보 조회파일 다운로드1
13474정성태12/6/20232133개발 환경 구성: 690. 닷넷 코어/5+ 버전의 ilasm/ildasm 실행 파일 구하는 방법 - 두 번째 이야기
1  2  3  4  [5]  6  7  8  9  10  11  12  13  14  15  ...