Lambda advanced

2022. 1. 20. 00:00CSharp/Advance

반응형

C# 10 에서 lambda 개선 사항

 

Attribute

속성이 있는 람다 허용

속성은 람다 식 및 람다 매개 변수에 추가할 수 있다.

메서드 특성과 매개변수 특성 간의 모호성을 피하기 위해

특성이 있는 람다 표현식은 괄호로 묶인 매개변수 목록을 사용해야 한다.

매개변수 유형은 필요하지 않다.

 

f = [A] () => { };        // [A] lambda
f = [return:A] x => x;    // syntax error at '=>'
f = [return:A] (x) => x;  // [A] lambda
f = [A] static x => x;    // syntax error at '=>'

f = ([A] x) => x;         // [A] x
f = ([A] ref int x) => x; // [A] x

동일한 속성 목록 내에서 쉼표로 구분하거나 별도의 속성 목록으로 여러 속성을 지정할 수 있다.

var f = [A1, A2][A3] () => { };    // ok
var g = ([A1][A2, A3] int x) => x; // ok

delegate  구문으로 선언된 익명 메서드 에는 특성이 지원되지 않는다.

var f = [A] delegate { return 1; };         // syntax error at 'delegate'
var g = delegate ([A] int x) { return x; }; // syntax error at '['

 

Explicit return type

명시적 반환 유형이 있는 람다 허용

 

C# 10부터는 람다 식의 반환 형식을 입력 매개 변수 앞에 지정할 수 있다.

명시적 반환 형식을 지정하는 경우 입력 매개 변수를 괄호로 묶어야 한다.

var choose = (bool b) => b ? 1 : "two"; // ERROR: Can't infer return type
var choose = object (bool b) => b ? 1 : "two"; // Func<bool, object>

 

delegate  구문으로 선언된 익명 메서드 에는 명시적 반환 형식이 지원되지 않는다.

f = delegate int { return 1; };         // syntax error
f = delegate int (int x) { return x; }; // syntax error

 

 

Natural (function) type

람다 및 메서드 그룹에 대한 자연 스러운 대리자 형식 유추

 

람다 식에서 대리자 형식을 유추할 수 있다.

var parse = (string s) => int.Parse(s);

컴파일러는 parse Func<string, int>일 것이라 유추할 수 있다. 

 

람다 식이 자연 형식을 갖는다면 System.Object 또는 System.Delegate과 같은 덜 명시적인 형식에 할당할 수 있다.

object parse = (string s) => int.Parse(s);   // Func<string, int>
Delegate parse = (string s) => int.Parse(s); // Func<string, int>

정확히 하나의 오버로드를 갖는 메서드 그룹(즉 매개 변수 목록이 없는 메서드 이름)은 자연 형식을 갖는다.

var read = Console.Read; // Just one overload; Func<int> inferred
var write = Console.Write; // ERROR: Multiple overloads, can't choose

System.Linq.Expressions.LambdaExpression 또는 System.Linq.Expressions.Expression에 람다 식을 할당했고 람다가 자연 대리자 형식을 갖는다면, 식은 System.Linq.Expressions.Expression<TDelegate> 자연 형식을 갖고 자연 대리자 형식은 형식 매개 변수의 인수로 사용된다.

LambdaExpression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
Expression parseExpr = (string s) => int.Parse(s);       // Expression<Func<string, int>>

 

자연형식을 갖지 않는 람다식

var parse = s => int.Parse(s); // ERROR: Not enough type info in the lambda

 

컴파일러가 s 의 형태를 유추할 수 없으므로 다음과 같이 해야 한다. 

Func<string, int> parse = s => int.Parse(s);

또는 아래와 같이 해도 된다. 컴파일러는 parse Func<string, int>일 것이라 유추할 수 있다. 

var parse = (string s)=>int.Parse(s);

 

람다 식에서 외부 변수 및 변수 범위 캡처

 

람다 식의 변수 범위에는 다음과 같은 규칙이 적용된다.

  • 캡처된 변수는 해당 변수를 참조하는 대리자가 가비지 수집 대상이 될 때까지 가비지 수집되지 않는다.
  • 람다 식에서 사용된 변수는 바깥쪽 메서드가 볼 수 없다.
  • 람다 식은 바깥쪽 메서드의 in, ref 또는 out 매개 변수를 직접 캡처할 수 없다.
  • 람다 식의 return 문에 의해서는 바깥쪽 메서드가 반환되지 않는다.
  • 해당 점프 문의 대상이 람다 식 블록 바깥에 있는 경우 람다 식은 goto, break 또는 continue 문을 포함할 수 없다. 대상이 블록 내에 있는 경우 람다 식 블록 외부에 점프 문을 사용해도 오류가 발생한다.
public static class VariableScopeWithLambdas
{
    public class VariableCaptureGame
    {
        internal Action<int> updateCapturedLocalVariable;
        internal Func<int, bool> isEqualToCapturedLocalVariable;

        public void Run(int input)
        {
            int j = 0;

            // 바깥쪽 j 를 update 함
            // j capture
            UpdateCapturedLocalVariable = x =>
            {
                j = x;
                bool result = j > input;
                Console.WriteLine($"{j} is greater than {input}: {result}");
            };

            // j 사용 
            // j capture
            IsEqualToCapturedLocalVariable = x => x == j;


            // run 내부에서 lambda 문인  UpdateCapturedLocalVariable 를 호출하여
            // j 의 값을 10으로 설정함
            // Run 함수를 벗어나면 일반 적으로 int j 는 초기화 되므로
            // UpdateCapturedLocalVariable 람다 문의 캡쳐된 j 나
            // IsEqualToCapturedLocalVariable 의 캡쳐된 j 도
            // 초기화 될것이라 생각할수 있으나 초기화 되지 않고 캡쳐한 값을 유지한다.
            // 즉 가비지시 수집되지 않는다. 
            Console.WriteLine($"Local variable before lambda invocation: {j}");
            UpdateCapturedLocalVariable(10);
            Console.WriteLine($"Local variable after lambda invocation: {j}");
        }
    }

    public static void Main()
    {
        var game = new VariableCaptureGame();

        int gameInput = 5;
        game.Run(gameInput);

        int jTry = 10;
        bool result = game.isEqualToCapturedLocalVariable(jTry);
        Console.WriteLine($"Captured local variable is equal to {jTry}: {result}");

        int anotherJ = 3;
        game.updateCapturedLocalVariable(anotherJ);

        bool equalToAnother = game.isEqualToCapturedLocalVariable(anotherJ);
        Console.WriteLine($"Another lambda observes a new value of captured variable: {equalToAnother}");
    }
    // Output:
    // Local variable before lambda invocation: 0
    // 10 is greater than 5: True
    // Local variable after lambda invocation: 10
    // Captured local variable is equal to 10: True
    // 3 is greater than 5: False
    // Another lambda observes a new value of captured variable: True
}

 

 

Syntax

lambda_expression
  : modifier* identifier '=>' (block | expression)
  | attribute_list* modifier* type? lambda_parameters '=>' (block | expression)
  ;

lambda_parameters
  : lambda_parameter
  | '(' (lambda_parameter (',' lambda_parameter)*)? ')'
  ;

lambda_parameter
  : identifier
  | attribute_list* modifier* type? identifier equals_value_clause?
  ;

 

관련영상

https://youtu.be/fEXMqIGgb60

 

 

 

 

반응형

'CSharp > Advance' 카테고리의 다른 글

제네릭 컬렉션 (Generic Collection)  (0) 2022.01.24
Record  (0) 2022.01.21
Action, Func, Lambda  (0) 2022.01.19
Event (이벤트)  (0) 2022.01.18
Delegate (대리자)  (0) 2022.01.17