Microsoft MVP성태의 닷넷 이야기
.NET Framework: 1109. C# 10 - (11) Lambda 개선 [링크 복사], [링크+제목 복사],
조회: 17457
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 15개 있습니다.)
.NET Framework: 1094. C# 10 - (1) 구조체를 생성하는 record struct
; https://www.sysnet.pe.kr/2/0/12790

.NET Framework: 1096. C# 10 - (2) 전역 네임스페이스 선언
; https://www.sysnet.pe.kr/2/0/12792

.NET Framework: 1097. C# 10 - (3) 개선된 변수 초기화 판정
; https://www.sysnet.pe.kr/2/0/12793

.NET Framework: 1099. C# 10 - (4) 상수 문자열에 포맷 식 사용 가능
; https://www.sysnet.pe.kr/2/0/12796

.NET Framework: 1100. C# 10 - (5) 속성 패턴의 개선
; https://www.sysnet.pe.kr/2/0/12799

.NET Framework: 1101. C# 10 - (6) record class 타입의 ToString 메서드를 sealed 처리 허용
; https://www.sysnet.pe.kr/2/0/12801

.NET Framework: 1103. C# 10 - (7) Source Generator V2 APIs
; https://www.sysnet.pe.kr/2/0/12804

.NET Framework: 1104. C# 10 - (8) 분해 구문에서 기존 변수의 재사용 가능
; https://www.sysnet.pe.kr/2/0/12805

.NET Framework: 1105. C# 10 - (9) 비동기 메서드가 사용할 AsyncMethodBuilder 선택 가능
; https://www.sysnet.pe.kr/2/0/12807

.NET Framework: 1108. C# 10 - (10) 개선된 #line 지시자
; https://www.sysnet.pe.kr/2/0/12812

.NET Framework: 1109. C# 10 - (11) Lambda 개선
; https://www.sysnet.pe.kr/2/0/12813

.NET Framework: 1113. C# 10 - (12) 문자열 보간 성능 개선
; https://www.sysnet.pe.kr/2/0/12826

.NET Framework: 1114. C# 10 - (13) 단일 파일 내에 적용되는 namespace 선언
; https://www.sysnet.pe.kr/2/0/12828

.NET Framework: 1115. C# 10 - (14) 구조체 타입에 기본 생성자 정의 가능
; https://www.sysnet.pe.kr/2/0/12829

.NET Framework: 1116. C# 10 - (15) CallerArgumentExpression 특성 추가
; https://www.sysnet.pe.kr/2/0/12835




C# 10 - (11) Lambda 개선

C# 10부터 3가지 부분에서 람다 사용법에 대한 개선이 이뤄졌습니다.

  1. 람다 식에 특성 사용
  2. 반환 타입 지정 허용
  3. 람다 식에 대한 추론 향상

하나씩 살펴볼까요? ^^

이미 C# 9.0부터 로컬 함수에 특성을 허용함으로써 다음과 같은 표현이 가능했는데요,

using System;

GetName();

[Obsolete]
string GetName() => "Anders";

이제는 람다 식 자체에도 특성을 지정할 수 있게 되었습니다.

using System.Diagnostics;
using System.Runtime.InteropServices;

var list = new List<string> { "Anders", "Kevin" };

// 람다 식에 특성 허용
list.ForEach([DebuggerHidden] (elem) => Console.WriteLine(elem));
// 매개 변수에도 특성 허용
list.ForEach(([CustomAttr] elem) => Console.WriteLine(elem));

Func<int> f1 =[return: MarshalAs(UnmanagedType.Bool)] () => 1;
Func<int> f2 =[return: CustomAttrAttribute] () => { return 1; };

Func<int, int> f3 =[return: CustomAttrAttribute] (arg) => { return arg + 1; };
// 특성을 적용하는 경우, 인자에 대한 괄호를 지정하지 않으면 컴파일 에러
// Error CS8916 Attributes on lambda expressions require a parenthesized parameter list.
// Func<int, int> f4 =[return: CustomAttrAttribute] arg => arg + 1;;
// Func<int, int> f4 =[return: CustomAttrAttribute] static arg => { return arg + 1; };

// AttributeTargets.Method가 지정된 특성은 람다 식에 적용 가능하지만,
// Conditional 특성은 예외적으로 람다 식에 적용하면 컴파일 에러
// error CS0577: The Conditional attribute is not valid on 'lambda expression' because it is a constructor, destructor, operator, lambda expression, or explicit interface implementation
// Action<int, int> f4 = ([Conditional("DEBUG")] static (x, y) => Debug.Assert(x == y));

public class CustomAttrAttribute : Attribute
{
}




그다음, 반환 타입에 대한 명시적인 지정을 허용하면서 다음의 표현들이 가능하게 바뀌었습니다.

// 람다 식에 반환값 명시
{
    Func<int, short> f1 = short (x) => 1;

    // 특성과 마찬가지로 반환 타입을 지정한 경우, 매개변수에 대한 괄호를 지정하지 않으면 컴파일 에러
    // error CS1525: Invalid expression term 'short'
    // Func<int, short> f = short x => 1;

    MethodRefDelegate f2 = (ref int (ref int x) => ref x);

    // ref 반환 타입의 경우 괄호가 없으면 컴파일 오류
    // error CS8171: Cannot initialize a by-value variable with a reference
    // MethodRefDelegate f = ref int (ref int x) => ref x;

    int number = 5;
    f2(ref number) = 10;
    Console.WriteLine($"number == {number}");

    Action<int> f3 = static void (_) => { };
}

public delegate ref int MethodRefDelegate(ref int arg);

public class MyType<T>
{
    public void Print()
    {
        Func<T> f = T () => default;
        Console.WriteLine($"T Result: {f()}");
    }
}

참고로 위에서 ref 반환 타입의 경우 괄호를 (현재는?) 명시해야만 컴파일이 되는데요, 이에 대해 문서에서 다음과 같이 언급하고 있습니다.

The parser should allow ref return types in assignment without parentheses.

Delegate d1 = (ref int () => x); // ok

// Visual Studio 2022 preview 3.1에서는 컴파일 오류
Delegate d2 = ref int () => x;   // ok

아마도 마지막 버전에서는 개선이 될지 지켜봐야겠습니다. ^^
(2022-07-12 업데이트: 현재 괄호 없이 컴파일 가능)




그리고, 마침내 람다 식에 대해서도 타입 추론이 적용되었습니다. 사실, 위에서 설명한 특성 지정이나 반환값 명시는 다소 특수한 경우여서 그다지 쓸 일이 많지는 않은데요, 반면 람다 식의 타입 추론은 꽤나 자주 쓸 수 있는 기능입니다.

이로 인해, 위에서 지금까지 실습했던 내용들에 대해 모두 "var"를 이용해 변수로 받는 것이 가능합니다.

// 타입 추론
{
    var f1 =[return: MarshalAs(UnmanagedType.Bool)] () => 1;
    var f2 =[return: CustomAttrAttribute] () => { return 1; };
    var f3 = short (int x) => 1;
    var f4 = (ref int (ref int x) => ref x);
    var f5 =[return: CustomAttrAttribute] (int arg) => { return arg + 1; };
}

물론, 위의 추론이 가능하려면 컴파일러에게 충분한 정보를 제공해야 합니다. 가령, 다음의 경우는 각각 모호한 부분이 있어 컴파일 오류가 발생하지만,

// 컴파일 오류 - error CS8917: The delegate type could not be inferred.
var f1 = () => default; // 반환 타입의 모호
var f2 = (x) => { }; // 매개 변수 타입의 모호
var f3 = x => x; // 반환 및 매개 변수 타입의 모호

사용자가 그에 따른 정보를 명시해 주면 정상적으로 추론이 이뤄지므로 컴파일이 잘 됩니다.

var f1 = int () => default; // 반환 타입 명시
var f2 = (int x) => { }; // 매개 변수 타입 명시
var f3 = (int x) => x; // 매개 변수의 타입이 명시됨으로써 반환 타입까지 추론

그러니까, 따지고 보면 C# 10에 추가된 "람다 식의 반환 타입 지정 허용"은 결국 타입 추론을 마무리하기 위한 목적에서도 필요했을 기능입니다.

그런데, 이것 역시 아직 Preview 단계여서 그런지 문서의 내용과 맞지 않는 부분들이 좀 있습니다. 가령, 다음의 코드는,

var fs = new[] { (string s) => s.Length, (string s) => int.Parse(s) } // Func<string, int>[]

분위기로 보아 배열 요소가 Func<string, int> 타입으로 추론될 것이므로 컴파일이 될 것처럼 나오지만 실제로는 오류가 발생합니다. 그래서 다음과 같이 (아직은?) 명시를 해야만 합니다.

var fs = new Func<string, int>[] { (string s) => s.Length, (string s) => int.Parse(s) };

또한 아래의 예제 코드도,

public static class MyEtension
{
    static void F1() { }

    public static void F1<T>(this T t) { }

    static void F2(this string s) { }

    public static void Write()
    {
        var f6 = F1; // F1 메서드와 F1<T> 중 어느 것을 선택해야 할지 알 수 없으므로 당연히 컴파일 오류
                     // error CS8917: The delegate type could not be inferred.

        // 문서에서는 System.Action으로 추론한다고 되어 있지만 실제로는 컴파일 오류
        // error CS8917: The delegate type could not be inferred.
        var f7 = "".F1;

        // 정상적으로 System.Action<string> 형식으로 추론
        var f8 = F2;
    }
}

문서와는 다르게 f7에 대해 컴파일 오류가 발생합니다.

이와 함께 타입 추론이 개선되어야 한다는 식의 언급도 있습니다.

Method type inference should make an exact inference from an explicit lambda return type.

static void F<T>(Func<T, T> f) { ... }
F(int (i) => i); // Func<int, int>

즉, 위의 코드에서 F(...) 호출의 람다식이 Func<int, int>로 추론되어야 한다고 설명은 하지만 아직도 이 코드는 "error CS0411: The type arguments for method 'F(Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly."라는 컴파일 오류가 발생합니다.




마지막으로, 결국 람다 식의 타입 추론이 가능하기 때문에, 명시적인 인스턴스를 생성하지 않고 직접 호출하는 것도 가능하므로 이에 대해 다음과 같은 "may be" 언급을 하고 있습니다.

Lambda expressions may be invoked directly. The compiler will generate a call to the underlying method without generating a delegate instance or synthesizing a delegate type. Directly invoked lambda expressions do not require explicit parameter types.


그래서 이렇게 호출하는 것도 가능할 거라고 하는데,

int zero = ((int x) => x)(0); // ok
int one = (x => x)(1);        // ok

아직은 아닙니다. ^^

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




C# 10 - (1) 구조체를 생성하는 record struct (공식 문서, Static Abstract Members In Interfaces C# 10 Preview)
; https://www.sysnet.pe.kr/2/0/12790

C# 10 - (2) 전역 네임스페이스 선언 (공식 문서, Global Using Directive)
; https://www.sysnet.pe.kr/2/0/12792

C# 10 - (3) 개선된 변수 초기화 판정 (공식 문서, Improved Definite Assignment)
; https://www.sysnet.pe.kr/2/0/12793

C# 10 - (4) 상수 문자열에 포맷 식 사용 가능 (공식 문서, Constant Interpolated Strings)
; https://www.sysnet.pe.kr/2/0/12796

C# 10 - (5) 속성 패턴의 개선 (공식 문서, Extended property patterns)
; https://www.sysnet.pe.kr/2/0/12799

C# 10 - (6) record class 타입의 ToString 메서드를 sealed 처리 허용 (공식 문서, Sealed record ToString)
; https://www.sysnet.pe.kr/2/0/12801

C# 10 - (7) Source Generator V2 APIs (Source Generator V2 APIs)
; https://www.sysnet.pe.kr/2/0/12804

C# 10 - (8) 분해 구문에서 기존 변수의 재사용 가능 (공식 문서, Mix declarations and variables in deconstruction)
; https://www.sysnet.pe.kr/2/0/12805

C# 10 - (9) 비동기 메서드가 사용할 AsyncMethodBuilder 선택 가능 (공식 문서, Async method builder override); 
; https://www.sysnet.pe.kr/2/0/12807

C# 10 - (10) 개선된 #line 지시자 (공식 문서, Enhanced #line directive)
; https://www.sysnet.pe.kr/2/0/12812

C# 10 - (11) Lambda 개선 (공식 문서 1, 공식 문서 2, Lambda improvements) 
; https://www.sysnet.pe.kr/2/0/12813

C# 10 - (12) 문자열 보간 성능 개선 (공식 문서, Interpolated string improvements)
; https://www.sysnet.pe.kr/2/0/12826

C# 10 - (13) 단일 파일 내에 적용되는 namespace 선언 (공식 문서, File-scoped namespace)
; https://www.sysnet.pe.kr/2/0/12828

C# 10 - (14) 구조체 타입에 기본 생성자 정의 가능 (공식 문서, Parameterless struct constructors)
; https://www.sysnet.pe.kr/2/0/12829

C# 10 - (15) CallerArgumentExpression 특성 추가 (공식 문서, Caller expression attribute)
; https://www.sysnet.pe.kr/2/0/12835

Language Feature Status
; https://github.com/dotnet/roslyn/blob/main/docs/Language%20Feature%20Status.md




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/14/2022]

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

비밀번호

댓글 작성자
 




... 106  107  108  109  110  [111]  112  113  114  115  116  117  118  119  120  ...
NoWriterDateCnt.TitleFile(s)
11150정성태2/21/201719344.NET Framework: 645. Visual Studio Fakes 기능에서 Shim... 클래스가 생성되지 않는 경우 [5]
11149정성태2/21/201723038오류 유형: 378. A 64-bit test cannot run in a 32-bit process. Specify platform as X64 to force test run in X64 mode on X64 machine.
11148정성태2/20/201721967.NET Framework: 644. AppDomain에 대한 단위 테스트 시 알아야 할 사항
11147정성태2/19/201721204오류 유형: 377. Windows 10에서 Fake 어셈블리를 생성하는 경우 빌드 시 The type or namespace name '...' does not exist in the namespace 컴파일 오류 발생
11146정성태2/19/201719892오류 유형: 376. Error VSP1033: The file '...' does not contain a recognized executable image. [2]
11145정성태2/16/201721347.NET Framework: 643. 작업자 프로세스(w3wp.exe)가 재시작되는 시점을 알 수 있는 방법 - 두 번째 이야기 [4]파일 다운로드1
11144정성태2/6/201724734.NET Framework: 642. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (부록 1) - CallingConvention.StdCall, CallingConvention.Cdecl에 상관없이 왜 호출이 잘 될까요?파일 다운로드1
11143정성태2/5/201722105.NET Framework: 641. [Out] 형식의 int * 인자를 가진 함수에 대한 P/Invoke 호출 방법파일 다운로드1
11142정성태2/5/201730123.NET Framework: 640. 닷넷 - 배열 크기의 한계 [2]파일 다운로드1
11141정성태1/31/201724411.NET Framework: 639. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (4) - CLR JIT 컴파일러의 P/Invoke 호출 규약 [1]파일 다운로드1
11140정성태1/27/201720158.NET Framework: 638. RSAParameters와 RSA파일 다운로드1
11139정성태1/22/201722870.NET Framework: 637. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (3) - x64 환경의 __fastcall과 Name mangling [1]파일 다운로드1
11138정성태1/20/201721146VS.NET IDE: 113. 프로젝트 생성 시부터 "Enable the Visual Studio hosting process" 옵션을 끄는 방법 - 두 번째 이야기 [3]
11137정성태1/20/201719838Windows: 135. AD에 참여한 컴퓨터로 RDP 연결 시 배경 화면을 못 바꾸는 정책
11136정성태1/20/201719024오류 유형: 375. Hyper-V 내에 구성한 Active Directory 환경의 시간 구성 방법 - 두 번째 이야기
11135정성태1/20/201720018Windows: 134. Windows Server 2016의 작업 표시줄에 있는 시계가 사라졌다면? [1]
11134정성태1/20/201727440.NET Framework: 636. System.Threading.Timer를 이용해 타이머 작업을 할 때 유의할 점 [5]파일 다운로드1
11133정성태1/20/201723544.NET Framework: 635. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (2) - x86 환경의 __fastcall [1]파일 다운로드1
11132정성태1/19/201735058.NET Framework: 634. C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (1) - x86 환경에서의 __cdecl, __stdcall에 대한 Name mangling [1]파일 다운로드1
11131정성태1/13/201723991.NET Framework: 633. C# - IL 코드 분석을 위한 팁 [2]
11130정성태1/11/201724504.NET Framework: 632. x86 실행 환경에서 SECURITY_ATTRIBUTES 구조체를 CreateEvent에 전달할 때 예외 발생파일 다운로드1
11129정성태1/11/201728881.NET Framework: 631. async/await에 대한 "There Is No Thread" 글의 부가 설명 [9]파일 다운로드1
11128정성태1/9/201723293.NET Framework: 630. C# - Interlocked.CompareExchange 사용 예제 [3]파일 다운로드1
11127정성태1/8/201722852기타: 63. (개발자를 위한) Visual Studio의 "with MSDN" 라이선스 설명
11126정성태1/7/201727583기타: 62. Edge 웹 브라우저의 즐겨찾기(Favorites)를 편집/백업/복원하는 방법 [1]파일 다운로드1
11125정성태1/7/201724429개발 환경 구성: 310. IIS - appcmd.exe를 이용해 특정 페이지에 클라이언트 측 인증서를 제출하도록 설정하는 방법
... 106  107  108  109  110  [111]  112  113  114  115  116  117  118  119  120  ...