람다 식(C# 프로그래밍 가이드)Lambda expressions (C# Programming Guide)

람다 식은 다음 두 형식의 식입니다.A lambda expression is an expression of any of the following two forms:

  • 식이 본문으로 포함된 식 람다:Expression lambda that has an expression as its body:

    (input-parameters) => expression
    
  • 문 블록이 본문으로 포함된 문 람다:Statement lambda that has a statement block as its body:

    (input-parameters) => { <sequence-of-statements> }
    

람다 선언 연산자=>를 사용하여 본문에서 람다의 매개 변수 목록을 구분합니다.Use the lambda declaration operator => to separate the lambda's parameter list from its body. 람다 식을 만들려면 람다 연산자 왼쪽에 입력 매개 변수를 지정하고(있는 경우) 다른 쪽에 식이나 문 블록을 지정합니다.To create a lambda expression, you specify input parameters (if any) on the left side of the lambda operator and an expression or a statement block on the other side.

람다 식은 대리자 형식으로 변환할 수 있습니다.Any lambda expression can be converted to a delegate type. 람다 식을 변환할 수 있는 대리자 형식은 해당 매개 변수 및 반환 값의 형식에 따라 정의됩니다.The delegate type to which a lambda expression can be converted is defined by the types of its parameters and return value. 람다 식에서 값을 반환하지 않는 경우 Action 대리자 형식 중 하나로 변환할 수 있습니다. 값을 반환하는 경우 Func 대리자 형식으로 변환할 수 있습니다.If a lambda expression doesn't return a value, it can be converted to one of the Action delegate types; otherwise, it can be converted to one of the Func delegate types. 예를 들어 매개 변수는 두 개지만 값을 반환하지 않는 람다 식은 Action<T1,T2> 대리자로 변환할 수 있습니다.For example, a lambda expression that has two parameters and returns no value can be converted to an Action<T1,T2> delegate. 매개 변수가 하나이고 값을 반환하는 람다 식은 Func<T,TResult> 대리자로 변환할 수 있습니다.A lambda expression that has one parameter and returns a value can be converted to a Func<T,TResult> delegate. 다음 예제에서는 x라고 이름이 지정되고 x 제곱 값을 반환하는 매개 변수를 지정하는 람다 식 x => x * x는 대리자 형식의 변수에 할당됩니다.In the following example, the lambda expression x => x * x, which specifies a parameter that’s named x and returns the value of x squared, is assigned to a variable of a delegate type:

Func<int, int> square = x => x * x;
Console.WriteLine(square(5));
// Output:
// 25

다음 예제에 표시된 대로 식 람다는 식 트리 형식으로도 변환할 수 있습니다.Expression lambdas also can be converted to the expression tree types, as the following example shows:

System.Linq.Expressions.Expression<Func<int, int>> e = x => x * x;
Console.WriteLine(e);
// Output:
// x => (x * x)

대리자 형식이나 식 트리의 인스턴스가 필요한 코드에서 람다 식을 백그라운드에서 실행해야 하는 코드를 전달하는 Task.Run(Action) 메서드의 인수 등으로 사용할 수 있습니다.You can use lambda expressions in any code that requires instances of delegate types or expression trees, for example as an argument to the Task.Run(Action) method to pass the code that should be executed in the background. 다음 예제에 표시된 대로 LINQ 쿼리 식을 쓸 때도 람다 식을 사용할 수 있습니다.You also can use lambda expressions when you write LINQ query expressions, as the following example shows:

int[] numbers = { 2, 3, 4, 5 };
var squaredNumbers = numbers.Select(x => x * x);
Console.WriteLine(string.Join(" ", squaredNumbers));
// Output:
// 4 9 16 25

예를 들어 LINQ to Objects 및 LINQ to XML에서 메서드 기반 구문을 사용하여 System.Linq.Enumerable 클래스에서 Enumerable.Select 메서드를 호출하는 경우 매개 변수는 대리자 형식 System.Func<T,TResult>입니다.When you use method-based syntax to call the Enumerable.Select method in the System.Linq.Enumerable class, for example in LINQ to Objects and LINQ to XML, the parameter is a delegate type System.Func<T,TResult>. 예를 들어 LINQ to SQL에서 System.Linq.Queryable 클래스에서 Queryable.Select 메서드를 호출하는 경우 매개 변수 형식은 식 트리 형식 Expression<Func<TSource,TResult>>입니다.When you call the Queryable.Select method in the System.Linq.Queryable class, for example in LINQ to SQL, the parameter type is an expression tree type Expression<Func<TSource,TResult>>. 두 경우 모두 동일한 람다 식을 사용하여 매개 변수 값을 지정할 수 있습니다.In both cases you can use the same lambda expression to specify the parameter value. 그러면 두 Select 호출이 비슷하게 보일 수 있지만 실제로 람다 식을 통해 생성되는 개체 형식은 다릅니다.That makes the two Select calls to look similar although in fact the type of objects created from the lambdas is different.

식 람다Expression lambdas

=> 연산자의 오른쪽에 식이 있는 람다 식을 식 람다라고 합니다. A lambda expression with an expression on the right side of the => operator is called an expression lambda. 식 람다는 식 트리를 만드는 데 광범위하게 사용됩니다.Expression lambdas are used extensively in the construction of expression trees. 식 람다는 식의 결과를 반환하며 기본 형식은 다음과 같습니다.An expression lambda returns the result of the expression and takes the following basic form:

(input-parameters) => expression

괄호는 람다 식에 입력 매개 변수가 하나뿐인 경우에만 생략할 수 있고 그렇지 않으면 생략할 수 없습니다.The parentheses are optional only if the lambda has one input parameter; otherwise they are required.

입력 매개 변수가 0개이면 다음과 같이 빈 괄호를 지정합니다.Specify zero input parameters with empty parentheses:

Action line = () => Console.WriteLine();

둘 이상의 입력 매개 변수는 다음과 같이 괄호로 묶고 쉼표로 구분해야 합니다.Two or more input parameters are separated by commas enclosed in parentheses:

Func<int, int, bool> testForEquality = (x, y) => x == y;

컴파일러에서 입력 형식을 유추할 수 없는 경우도 있습니다.Sometimes it's impossible for the compiler to infer the input types. 다음 예제와 같이 형식을 명시적으로 지정할 수 있습니다.You can specify the types explicitly as shown in the following example:

Func<int, string, bool> isTooLong = (int x, string s) => s.Length > x;

입력 매개 변수 형식은 모두 명시적이거나 암시적이어야 합니다. 그렇지 않으면 CS0748 컴파일러 오류가 발생합니다.Input parameter types must be all explicit or all implicit; otherwise, a CS0748 compiler error occurs.

식 람다의 본문은 메서드 호출로 구성될 수 있습니다.The body of an expression lambda can consist of a method call. 하지만 SQL Server와 같은 .NET 공용 언어 런타임의 컨텍스트 외부에서 평가되는 식 트리를 만드는 경우에는 람다 식에 메서드 호출을 사용하지 않아야 합니다.However, if you are creating expression trees that are evaluated outside the context of the .NET common language runtime, such as in SQL Server, you should not use method calls in lambda expressions. 이러한 메서드는 .NET 공용 언어 런타임의 컨텍스트 안에서만 의미가 있습니다.The methods will have no meaning outside the context of the .NET common language runtime.

문 람다Statement lambdas

문 람다는 다음과 같이 중괄호 안에 문을 지정한다는 점을 제외하면 식 람다와 비슷합니다.A statement lambda resembles an expression lambda except that the statement(s) is enclosed in braces:

(input-parameters) => { <sequence-of-statements> }

문 람다의 본문에 지정할 수 있는 문의 개수에는 제한이 없지만 일반적으로 2-3개 정도만 지정합니다.The body of a statement lambda can consist of any number of statements; however, in practice there are typically no more than two or three.

Action<string> greet = name => 
{ 
    string greeting = $"Hello {name}!";
    Console.WriteLine(greeting);
};
greet("World");
// Output:
// Hello World!

문 람다는 식 트리를 만드는 데 사용할 수 없습니다.Statement lambdas cannot be used to create expression trees.

비동기 람다Async lambdas

asyncawait 키워드를 사용하여 비동기 처리를 통합하는 람다 식과 문을 쉽게 만들 수 있습니다.You can easily create lambda expressions and statements that incorporate asynchronous processing by using the async and await keywords. 예를 들어 다음 Windows Forms 예제에는 비동기 메서드 ExampleMethodAsync를 호출하고 기다리는 이벤트 처리기가 포함되어 있습니다.For example, the following Windows Forms example contains an event handler that calls and awaits an async method, ExampleMethodAsync.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        button1.Click += button1_Click;
    }

    private async void button1_Click(object sender, EventArgs e)
    {
        await ExampleMethodAsync();
        textBox1.Text += "\r\nControl returned to Click event handler.\n";
    }

    private async Task ExampleMethodAsync()
    {
        // The following line simulates a task-returning asynchronous process.
        await Task.Delay(1000);
    }
}

비동기 람다를 사용하여 동일한 이벤트 처리기를 추가할 수 있습니다.You can add the same event handler by using an async lambda. 이 처리기를 추가하려면 다음 예제에 표시된 것처럼 람다 매개 변수 목록에 async 한정자를 추가합니다.To add this handler, add an async modifier before the lambda parameter list, as the following example shows:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        button1.Click += async (sender, e) =>
        {
            await ExampleMethodAsync();
            textBox1.Text += "\r\nControl returned to Click event handler.\n";
        };
    }

    private async Task ExampleMethodAsync()
    {
        // The following line simulates a task-returning asynchronous process.
        await Task.Delay(1000);
    }
}

비동기 메서드를 만들고 사용하는 방법에 대한 자세한 내용은 Async 및 Await를 사용한 비동기 프로그래밍을 참조하세요.For more information about how to create and use async methods, see Asynchronous Programming with async and await.

람다 식 및 튜플Lambda expressions and tuples

C# 7.0부터 C# 언어에서 튜플을 기본적으로 지원합니다.Starting with C# 7.0, the C# language provides built-in support for tuples. 람다 식에 인수로 튜플을 제공할 수 있으며 람다 식에서 튜플을 반환할 수도 있습니다.You can provide a tuple as an argument to a lambda expression, and your lambda expression can also return a tuple. 경우에 따라 C# 컴파일러는 형식 유추를 사용하여 튜플 구성 요소의 형식을 확인할 수 있습니다.In some cases, the C# compiler uses type inference to determine the types of tuple components.

쉼표로 구분된 해당 구성 요소 목록을 괄호로 묶어 튜플을 정의합니다.You define a tuple by enclosing a comma-delimited list of its components in parentheses. 다음 예제에서는 3개 구성 요소가 있는 튜플을 사용하여 숫자 시퀀스를 람다 식에 전달하고 각 값을 두 배로 늘린 후 곱하기의 결과가 포함된, 3개 구성 요소가 있는 튜플을 반환합니다.The following example uses tuple with three components to pass a sequence of numbers to a lambda expression, which doubles each value and returns a tuple with three components that contains the result of the multiplications.

Func<(int, int, int), (int, int, int)> doubleThem = ns => (2 * ns.Item1, 2 * ns.Item2, 2 * ns.Item3);
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}");
// Output:
// The set (2, 3, 4) doubled: (4, 6, 8)

일반적으로 튜플 필드의 이름은 Item1, Item2 등입니다. 그러나 다음 예제에서처럼 명명된 구성 요소가 있는 튜플을 정의할 수 있습니다.Ordinarily, the fields of a tuple are named Item1, Item2, etc. You can, however, define a tuple with named components, as the following example does.

Func<(int n1, int n2, int n3), (int, int, int)> doubleThem = ns => (2 * ns.n1, 2 * ns.n2, 2 * ns.n3);
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}");

C# 튜플에 대한 자세한 내용은 C# 튜플 형식을 참조하세요.For more information about C# tuples, see C# tuple types.

표준 쿼리 연산자와 람다 식Lambdas with the standard query operators

다른 구현 중에 LINQ to Objects는 형식이 제네릭 대리자의 Func<TResult> 패밀리 중 하나인 입력 매개 변수를 사용합니다.LINQ to Objects, among other implementations, have an input parameter whose type is one of the Func<TResult> family of generic delegates. 이러한 대리자는 형식 매개 변수를 사용하여 입력 매개 변수의 수와 형식 및 대리자의 반환 형식을 정의합니다.These delegates use type parameters to define the number and type of input parameters, and the return type of the delegate. Func 대리자는 소스 데이터 집합에 있는 각 요소에 적용할 사용자 정의 식을 캡슐화하는 데 매우 유용합니다.Func delegates are very useful for encapsulating user-defined expressions that are applied to each element in a set of source data. 예를 들어 Func<T,TResult> 대리자 형식을 고려합니다.For example, consider the Func<T,TResult> delegate type:

public delegate TResult Func<in T, out TResult>(T arg)

이 경우 대리자를 Func<int, bool> 인스턴스로 인스턴스화할 수 있습니다. 여기서 int는 입력 매개 변수이고, bool은 반환 값입니다.The delegate can be instantiated as a Func<int, bool> instance where int is an input parameter and bool is the return value. 반환 값은 항상 마지막 형식 매개 변수에 지정됩니다.The return value is always specified in the last type parameter. 예를 들어 Func<int, string, bool>은 두 입력 매개 변수 intstring과 반환 형식 bool을 사용하여 대리자를 정의합니다.For example, Func<int, string, bool> defines a delegate with two input parameters, int and string, and a return type of bool. 다음 Func 대리자를 호출하면 입력 매개 변수가 5인지 여부를 나타내는 부울 값이 반환됩니다.The following Func delegate, when it's invoked, returns Boolean value that indicates whether the input parameter is equal to five:

Func<int, bool> equalsFive = x => x == 5;
bool result = equalsFive(4);
Console.WriteLine(result);   // False

Queryable 형식에 정의되어 있는 표준 쿼리 연산자의 경우와 같이 인수 형식이 Expression<TDelegate>인 경우에도 람다 식을 사용할 수 있습니다.You can also supply a lambda expression when the argument type is an Expression<TDelegate>, for example in the standard query operators that are defined in the Queryable type. Expression<TDelegate> 인수를 지정하면 람다 식이 식 트리로 컴파일됩니다.When you specify an Expression<TDelegate> argument, the lambda is compiled to an expression tree.

이 예제에서는 Count 표준 쿼리 연산자를 사용합니다.The following example uses the Count standard query operator:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);
Console.WriteLine($"There are {oddNumbers} odd numbers in {string.Join(" ", numbers)}");

컴파일러에서 입력 매개 변수의 형식을 유추하거나 사용자가 형식을 명시적으로 지정할 수 있습니다.The compiler can infer the type of the input parameter, or you can also specify it explicitly. 이 람다 식은 2로 나누었을 때 나머지가 1인 정수(n)의 수를 계산합니다.This particular lambda expression counts those integers (n) which when divided by two have a remainder of 1.

다음 예제에서는 숫자 시퀀스에서 조건을 만족하지 않는 첫 번째 숫자가 9이기 때문에 numbers 배열에서 9 앞에 오는 모든 요소가 포함된 시퀀스를 생성합니다.The following example produces a sequence that contains all elements in the numbers array that precede the 9, because that's the first number in the sequence that doesn't meet the condition:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstNumbersLessThanSix = numbers.TakeWhile(n => n < 6);
Console.WriteLine(string.Join(" ", firstNumbersLessThanSix));
// Output:
// 5 4 1 3

다음 예제에서는 입력 매개 변수를 괄호로 묶어 여러 개 지정합니다.The following example specifies multiple input parameters by enclosing them in parentheses. 이 메서드는 값이 배열의 서수 위치보다 작은 숫자가 나타날 때까지 numbers 배열의 모든 요소를 반환합니다.The method returns all the elements in the numbers array until it encounters a number whose value is less than its ordinal position in the array:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);
Console.WriteLine(string.Join(" ", firstSmallNumbers));
// Output:
// 5 4

람다 식에서의 형식 유추Type inference in lambda expressions

컴파일러에서는 람다 식 본문, 매개 변수 형식 및 C# 언어 사양에 설명되어 있는 기타 요소를 기준으로 형식을 유추할 수 있기 때문에 대부분의 경우에는 람다 식을 작성할 때 입력 매개 변수의 형식을 지정하지 않아도 됩니다.When writing lambdas, you often don't have to specify a type for the input parameters because the compiler can infer the type based on the lambda body, the parameter types, and other factors as described in the C# language specification. 대부분의 표준 쿼리 연산자에서 첫 번째 입력 형식은 소스 시퀀스 요소의 형식입니다.For most of the standard query operators, the first input is the type of the elements in the source sequence. IEnumerable<Customer>를 쿼리할 경우 입력 변수가 Customer 개체로 유추됩니다. 이는 이 개체의 메서드와 속성에 액세스할 수 있음을 의미합니다.If you are querying an IEnumerable<Customer>, then the input variable is inferred to be a Customer object, which means you have access to its methods and properties:

customers.Where(c => c.City == "London");

람다 식의 형식 유추에 대한 일반적인 규칙은 다음과 같습니다.The general rules for type inference for lambdas are as follows:

  • 람다 식과 대리자 형식에 포함된 매개 변수 수가 같아야 합니다.The lambda must contain the same number of parameters as the delegate type.

  • 람다 식의 각 입력 매개 변수는 해당되는 대리자 매개 변수로 암시적으로 변환될 수 있어야 합니다.Each input parameter in the lambda must be implicitly convertible to its corresponding delegate parameter.

  • 람다 식의 반환 값(있는 경우)은 대리자의 반환 형식으로 암시적으로 변환될 수 있어야 합니다.The return value of the lambda (if any) must be implicitly convertible to the delegate's return type.

공용 형식 시스템에는 “람다 식”이라는 개념이 기본적으로 포함되어 있지 않기 때문에 람다 식 자체에는 형식이 없습니다.Note that lambda expressions in themselves don't have a type because the common type system has no intrinsic concept of "lambda expression." 그러나 람다 식의 “형식”을 비공식적으로 언급해야 할 경우도 있는데However, it's sometimes convenient to speak informally of the "type" of a lambda expression. 이 경우 형식은 대리자 형식 또는 람다 식이 변환되는 Expression 형식을 의미합니다.In these cases the type refers to the delegate type or Expression type to which the lambda expression is converted.

람다 식에서 외부 변수 및 변수 범위 캡처Capture of outer variables and variable scope in lambda expressions

람다는 외부 변수를 참조할 수 있습니다.Lambdas can refer to outer variables. 이러한 변수는 람다 식을 정의하는 메서드 범위 내에 있거나 람다 식을 포함하는 형식 범위 내에 있는 변수입니다.These are the variables that are in scope in the method that defines the lambda expression, or in scope in the type that contains the lambda expression. 이러한 방식으로 캡처되는 변수는 변수가 범위를 벗어나 가비지 수집되는 경우에도 람다 식에 사용할 수 있도록 저장됩니다.Variables that are captured in this manner are stored for use in the lambda expression even if the variables would otherwise go out of scope and be garbage collected. 외부 변수는 명확하게 할당해야만 람다 식에 사용할 수 있습니다.An outer variable must be definitely assigned before it can be consumed in a lambda expression. 다음 예제에서는 이러한 규칙을 보여 줍니다.The following example demonstrates these rules:

public static class VariableScopeWithLambdas
{
    public class VariableCaptureGame
    {
        internal Action<int> updateCapturedLocalVariable;
        internal Func<int, bool> isEqualToCapturedLocalVariable;

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

            updateCapturedLocalVariable = x =>
            {
                j = x;
                bool result = j > input;
                Console.WriteLine($"{j} is greater than {input}: {result}");
            };

            isEqualToCapturedLocalVariable = x => x == 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
}

람다 식의 변수 범위에는 다음과 같은 규칙이 적용됩니다.The following rules apply to variable scope in lambda expressions:

  • 캡처된 변수는 해당 변수를 참조하는 대리자가 가비지 수집 대상이 될 때까지 가비지 수집되지 않습니다.A variable that is captured will not be garbage-collected until the delegate that references it becomes eligible for garbage collection.

  • 람다 식에 사용된 변수는 바깥쪽 메서드에 표시되지 않습니다.Variables introduced within a lambda expression are not visible in the enclosing method.

  • 람다 식은 바깥쪽 메서드에서 in, ref 또는 out 매개 변수를 직접 캡처할 수 없습니다.A lambda expression cannot directly capture an in, ref, or out parameter from the enclosing method.

  • 람다 식의 return 문에 의해서는 바깥쪽 메서드가 반환되지 않습니다.A return statement in a lambda expression doesn't cause the enclosing method to return.

  • 해당 점프 문의 대상이 람다 식 블록을 벗어나는 경우 람다 식에는 goto, break 또는 continue 문을 포함할 수 없습니다.A lambda expression cannot contain a goto, break, or continue statement if the target of that jump statement is outside the lambda expression block. 대상이 블록 내에 있는 경우 람다 식 블록 외부에 점프 문을 사용해도 오류가 발생합니다.It's also an error to have a jump statement outside the lambda expression block if the target is inside the block.

C# 언어 사양C# language specification

자세한 내용은 C# 언어 사양익명 함수 식 섹션을 참조하세요.For more information, see the Anonymous function expressions section of the C# language specification.

대리자, Events, and Lambda Expressions C# 3.0 Cookbook, Third Edition: 250 개 이상의 솔루션에 대 한 C# 3.0 프로그래머Delegates, Events, and Lambda Expressions in C# 3.0 Cookbook, Third Edition: More than 250 solutions for C# 3.0 programmers

참고 항목See also