성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# 9.0 - (10) 대상으로 형식화된 조건식(Target-typed conditional expressions)</h1> <p> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# 9.0 - (1) <a target='tab' href='https://github.com/dotnet/csharplang/issues/100'>대상으로 형식화된 new 식(Target-typed new expressions)</a> ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12363'>https://www.sysnet.pe.kr/2/0/12363</a> C# 9.0 - (2) <a target='tab' href='https://github.com/dotnet/csharplang/issues/1738'>localsinit 플래그 내보내기 무시(Suppress emitting localsinit flag)</a> ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12364'>https://www.sysnet.pe.kr/2/0/12364</a> C# 9.0 - (3) <a target='tab' href='https://github.com/dotnet/csharplang/issues/111'>람다 메서드의 매개 변수 무시(Lambda discard parameters)</a> ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12365'>https://www.sysnet.pe.kr/2/0/12365</a> C# 9.0 - (4) <a target='tab' href='https://github.com/dotnet/csharplang/issues/435'>원시 크기 정수(Native ints)</a> ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12366'>https://www.sysnet.pe.kr/2/0/12366</a> C# 9.0 - (5) <a target='tab' href='https://github.com/dotnet/csharplang/issues/1888'>로컬 함수에 특성 지정 가능(Attributes on local functions)</a> ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12372'>https://www.sysnet.pe.kr/2/0/12372</a> C# 9.0 - (6) <a target='tab' href='https://github.com/dotnet/csharplang/issues/191'>함수 포인터(Function pointers)</a> ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12374'>https://www.sysnet.pe.kr/2/0/12374</a> C# 9.0 - (7) <a target='tab' href='https://github.com/dotnet/csharplang/issues/2850'>패턴 일치 개선 사항(Pattern matching enhancements)</a> ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12383'>https://www.sysnet.pe.kr/2/0/12383</a> C# 9.0 - (8) <a target='tab' href='https://github.com/dotnet/csharplang/issues/275'>정적 익명 함수 (static anonymous functions)</a> ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12389'>https://www.sysnet.pe.kr/2/0/12389</a> C# 9.0 - (9) <a target='tab' href='https://github.com/dotnet/csharplang/issues/39'>레코드 (Records)</a> ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12392'>https://www.sysnet.pe.kr/2/0/12392</a> C# 9.0 - (10) <a target='tab' href='https://github.com/dotnet/csharplang/issues/2460'>대상으로 형식화된 조건식(Target-typed conditional expressions)</a> ; https://www.sysnet.pe.kr/2/0/12399 C# 9.0 - (11) <a target='tab' href='https://github.com/dotnet/csharplang/issues/2844'>공변 반환 형식(Covariant return types)</a> ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12402'>https://www.sysnet.pe.kr/2/0/12402</a> C# 9.0 - (12) <a target='tab' href='https://github.com/dotnet/csharplang/issues/3194'>foreach 루프에 대한 GetEnumerator 확장 메서드 지원(Extension GetEnumerator)</a> ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12403'>https://www.sysnet.pe.kr/2/0/12403</a> C# 9.0 - (13) <a target='tab' href='https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/module-initializers.md'>모듈 이니셜라이저(Module initializers)</a> ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12404'>https://www.sysnet.pe.kr/2/0/12404</a> C# 9.0 - (14) <a target='tab' href='https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/extending-partial-methods.md'>부분 메서드에 대한 새로운 기능(New features for partial methods)</a> ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12405'>https://www.sysnet.pe.kr/2/0/12405</a> C# 9.0 - (15) <a target='tab' href='https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/top-level-statements.md'>최상위 문(Top-level statements)</a> ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12406'>https://www.sysnet.pe.kr/2/0/12406</a> C# 9.0 - (16) <a target='tab' href='https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/unconstrained-type-parameter-annotations'>제약 조건이 없는 형식 매개변수 주석(Unconstrained type parameter annotations)</a> ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12423'>https://www.sysnet.pe.kr/2/0/12423</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> (이번 실습은 - 오늘 기준 16.7.7의 Visual Studio 2019에서 지원하지 않으므로 Visual Studio Preview 버전으로 실습해야 합니다.)<br /> <br /> 일반적으로 조건 연산자는 2항과 3항의 식이 어느 한 쪽으로든 암시적 형 변환이 가능한 유형이어야 합니다. 만약 그것이 불가능한 경우에는 컴파일 오류가 발생하는데요, 예를 들어 다음의 예제가 그런 경우입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // <a target='tab' href='https://anthonygiretti.com/2020/06/21/introducing-c-9-improved-target-typing/'>https://anthonygiretti.com/2020/06/21/introducing-c-9-improved-target-typing/</a> 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' <span style='color: blue; font-weight: bold'>Product prd = aBook != null ? aBook : headset;</span> } { Product prd = null; if (aBook != null) prd = aBook; else prd = headset; } } } public class Product { } public class Book : Product { } public class Headset : Product { } </pre> <br /> 왜냐하면, 개별적으로는 Book과 Headset 타입이 Product로 암시적 형변환은 가능하지만, Book과 Headset 중 어느 한 쪽으로의 형변환은 가능하지 않기 때문입니다. 만약 이 상태에서 컴파일을 정상적으로 하고 싶다면 2가지 방법이 있습니다. 하나는 Book이나 Headset 타입 중에서 암시적 형변환 연산자를 재정의하는 것입니다. 예를 들어, Book에 Headset으로의 암시적 형변환을 가능하게 만들면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class Book : Product { public static <span style='color: blue; font-weight: bold'>implicit operator</span> Headset(Book instance) => new Headset(); } </pre> <br /> 이제 조건 연산자는, 2항과 3항 식의 타입이 Headset으로 일치하게 되고, Headset 또한 (당연히 기반 타입의) Product 타입으로 암시적 형변환이 가능하므로 아무런 오류 없이 컴파일을 합니다.<br /> <br /> 혹은, 위와 같이 (억지로) 암시적 형변환을 할 수 없는 상황이라면 단순히 cast 연산자를 직접 써도 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Product prd = aBook != null ? (Product)aBook : headset; </pre> <br /> 그럼 2항의 식이 Product 타입으로 평가되고, 3항의 headset 또한 2항의 Product 타입으로 암시적 형변환이 가능하게 바뀝니다.<br /> <br /> 이런 불편함을, C# 9.0부터 새롭게 도입한 조건 식의 암시적 형변환 규칙으로 인해 2항과 3항의 타입이 대상 타입으로 암시적 형변환이 가능하다면 C# 컴파일러가 대상 타입을 맞춰주기(target-typed)로 한 것입니다.<br /> <br /> 이 규칙은 null 리터럴이 사용되는 경우에도 적용됩니다. 가령 다음의 코드는 C# 8.0 이전에는 오류가 발생했지만,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // C# 8.0 이전: 컴파일 오류 // Error CS0173 Type of conditional expression cannot be determined because there is no implicit conversion between 'int' and '<null>' int? result = aBook != null ? 0 : null; </pre> <br /> 9.0 이후부터는 2항의 0과 3항의 null이 int? 타입으로 대입이 가능하므로 컴파일러가 알아서 target-typed 처리를 해줍니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이와 함께 한 가지 더 변화가 생겼는데요. 예를 들어 다음의 조건 연산자가 int 없이 short와 long만 인자로 받는 메서드가 있는 상황에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // C# - 조건 연산자(?:)를 사용하는 경우 달라지는 메서드 선택 사례 // <a target='tab' href='https://www.sysnet.pe.kr/2/0/12397'>https://www.sysnet.pe.kr/2/0/12397</a> using System; class Program { static void Main(string[] args) { M(<span style='color: blue; font-weight: bold'>args.Length == 0 ? 1 : 2</span>); } static void M(short n) { Console.WriteLine("Short"); } static void M(long n) { Console.WriteLine("Long"); } } /* 출력 결과 Long */ </pre> <br /> M(long) 버전이 선택되지만 비슷한 수식으로 C# 8.0 이후 나온 switch 식의 경우엔,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > M((args.Length == 1) switch { true => 1, false => 2 }); // 출력 결과: Short </pre> <br /> short로 평가되어 그 결과가 서로 다르다는 문제가 있습니다.<br /> <br /> 왜냐하면, switch 식의 경우 모든 case에 해당하는 결과를 최종 반환 타입의 평가에 사용하기 때문입니다. 즉, 타입 추론 기능이 좋아진 것입니다. (동일한 타입 추론 규칙을 조건 연산자에도 적용할 수 있었겠지만, 그런 경우 하위 호환성이 없어지므로 기존 프로그램을 다시 빌드하면 동작이 달라지는 문제가 발생하므로 그대로 남긴 듯합니다.)<br /> <br /> 그런데, C# 9.0부터는 이런 식의 불일치를 최대한 막기 위한 제약을 추가했습니다. 예를 들어, 다음과 같은 코드는 C# 8.0 까지는,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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 */ </pre> <br /> 정상적으로 빌드가 되었습니다. 첫 번째 인자의 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12397'>"args.Length == 0 ? 1 : 2" 조건식은 int에서 long으로 암시적 형변환이 이뤄지고, 두 번째 인자는 암시적 형변환이 이뤄진 short</a>로 일치하는 타입의 메서드가 있음에도 long 형으로 다시 암시적 형변환이 일어나 결국 M(long, long) 버전의 메서드가 선택된 것입니다.<br /> <br /> 이런 평가는, 인자가 하나만 있을 때와 비교해 타입 평가가 점점 더 틀어지고 있다고 볼 수 있습니다. 왜냐하면 두 번째 인자와 정확히 일치하는 타입이 있는데도 불구하고 첫 번째 인자가 C# 1.0 때부터의 호환을 지키기 위해 int -> long으로 형변환을 발생시켜 맞춰 나가고 있기 때문입니다. 타입 추론이 강화되면서 점차 short로도 판정할 수 있지만 차마 하지 못했던 것을, 즉, 바로 잡지는 못할망정 점점 더 어긋나는 것은 막기 위해 제약을 두기 시작한 것입니다. 물론, 그렇다고 그런 경우에만 조건 연산자의 결과를 short로 판정하는 것은 또 규칙에 어긋나기 때문에 아예 모호하다는 컴파일 오류를 발생하기로 결정한 듯합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 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); </pre> <br /> 그래서, 기존 소스를 빌드하는 중에 위와 같은 오류가 발생한다면 개발자는 명시적으로 형변환을 추가해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > M((short)(args.Length == 1 ? 1 : 2, 1)); // M(short, short) M((long)(args.Length == 1 ? 1 : 2, 1)); // M(long, long) </pre> <br /> 참고로, 문서에 보면 이런 변화에 대해 "This breaking change seems less serious because it does not silently change the behavior of an existing program."라고 언급합니다.<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1650&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1012
(왼쪽의 숫자를 입력해야 합니다.)