람다 식 및 익명 함수
람다 식 사용하여 익명 함수를 만듭니다.
람다 선언 연산자 =>
사용하여 람다의 매개 변수 목록을 본문과 구분합니다. 람다 식은 다음 두 가지 형식 중 어느 것일 수 있습니다.
-
(input-parameters) => expression
-
(input-parameters) => { <sequence-of-statements> }
람다 식을 만들려면 람다 연산자의 왼쪽에 입력 매개 변수(있는 경우)를 지정하고 다른 쪽에는 식 또는 문 블록을 지정합니다.
모든 람다 식을 대리자 형식으로 변환할 수 있습니다. 해당 매개 변수 및 반환 값의 형식은 람다 식을 변환할 수 있는 대리자 형식을 정의합니다. 람다 식이 값을 반환하지 않으면 Action
대리자 형식 중 하나로 변환할 수 있습니다. 그렇지 않으면 Func
대리자 형식 중 하나로 변환할 수 있습니다. 예를 들어 두 개의 매개 변수가 있고 값을 반환하지 않는 람다 식은 Action<T1,T2> 대리자로 변환할 수 있습니다. 매개 변수가 하나 있고 값을 반환하는 람다 식은 Func<T,TResult> 대리자로 변환할 수 있습니다. 다음 예제에서는 x
매개 변수를 지정하고 x
제곱 값을 반환하는 람다 식 x => x * x
대리자 형식의 변수에 할당됩니다.
Func<int, int> square = x => x * x;
Console.WriteLine(square(5));
// Output:
// 25
식 람다는 또한 다음 예제가 보여주듯이, 식 트리 형식으로 변환될 수 있습니다.
System.Linq.Expressions.Expression<Func<int, int>> e = x => x * x;
Console.WriteLine(e);
// Output:
// x => (x * x)
대리자 형식 또는 식 트리의 인스턴스가 필요한 모든 코드에서 람다 식을 사용합니다. 한 가지 예는 백그라운드에서 실행해야 하는 코드를 전달하는 Task.Run(Action) 메서드에 대한 인수입니다. 다음 예제와 같이 C#
int[] numbers = { 2, 3, 4, 5 };
var squaredNumbers = numbers.Select(x => x * x);
Console.WriteLine(string.Join(" ", squaredNumbers));
// Output:
// 4 9 16 25
메서드 기반 구문을 사용하여 System.Linq.Enumerable 클래스에서 Enumerable.Select 메서드를 호출하는 경우(예: LINQ to Objects 및 LINQ to XML) 매개 변수는 System.Func<T,TResult>대리자 형식입니다. 예를 들어 LINQ to SQL에서 System.Linq.Queryable 클래스에서 Queryable.Select 메서드를 호출할 때 매개 변수 형식은 Expression<Func<TSource,TResult>>
식 트리 형식입니다. 두 경우 모두 동일한 람다 식을 사용하여 매개 변수 값을 지정할 수 있습니다. 실제로 람다에서 만든 개체의 형식은 다르지만 두 Select
호출은 비슷하게 표시됩니다.
람다 식
=>
연산자의 오른쪽에 표현식이 있는 람다 식을 표현식 람다라고 합니다. 식 람다는 식의 결과를 반환하고 다음 기본 형식을 사용합니다.
(input-parameters) => expression
식 람다의 본문은 메서드 호출로 구성됩니다. 그러나 SQL Server와 같이 .NET CLR(공용 언어 런타임)의 컨텍스트 외부에서 평가되는
문형 람다
문장 람다는 그 문장의 내용이 중괄호로 묶인다는 점을 제외하면 식 람다와 비슷합니다.
(input-parameters) => { <sequence-of-statements> }
문 람다의 본문은 임의의 수의 문으로 구성될 수 있습니다. 그러나 실제로는 일반적으로 두세 개를 넘지 않습니다.
Action<string> greet = name =>
{
string greeting = $"Hello {name}!";
Console.WriteLine(greeting);
};
greet("World");
// Output:
// Hello World!
문형 람다를 사용하여 식 트리를 만들 수 없습니다.
람다 식의 입력 매개 변수
람다 식의 입력 매개 변수를 괄호로 묶습니다. 빈 괄호로 입력 매개 변수 0을 지정합니다.
Action line = () => Console.WriteLine();
람다 식에 입력 매개 변수가 하나만 있는 경우 괄호는 선택 사항입니다.
Func<double, double> cube = x => x * x * x;
두 개 이상의 입력 매개 변수는 쉼표로 구분됩니다.
Func<int, int, bool> testForEquality = (x, y) => x == y;
컴파일러가 입력 매개 변수 형식을 유추할 수 없는 경우가 있습니다. 다음 예제와 같이 형식을 명시적으로 지정할 수 있습니다.
Func<int, string, bool> isTooLong = (int x, string s) => s.Length > x;
입력 매개 변수 형식은 모두 명시적이거나 모두 암시적이어야 합니다. 그렇지 않으면 CS0748 컴파일러 오류가 발생합니다.
무시 사용하여 식에 사용되지 않는 람다 식의 입력 매개 변수를 두 개 이상 지정할 수 있습니다.
Func<int, int, int> constant = (_, _) => 42;
람다 삭제 매개 변수는 이벤트 처리기제공하기
메모
이전 버전과의 호환성을 위해 단일 입력 매개 변수의 이름이 _
경우 람다 식 내에서 _
해당 매개 변수의 이름으로 처리됩니다.
C# 12부터 람다 식의 매개 변수에
var IncrementBy = (int source, int increment = 1) => source + increment;
Console.WriteLine(IncrementBy(5)); // 6
Console.WriteLine(IncrementBy(5, 2)); // 7
params
배열 또는 컬렉션을 매개 변수로 사용하여 람다 식을 선언할 수도 있습니다.
var sum = (params IEnumerable<int> values) =>
{
int sum = 0;
foreach (var value in values)
sum += value;
return sum;
};
var empty = sum();
Console.WriteLine(empty); // 0
var sequence = new[] { 1, 2, 3, 4, 5 };
var total = sum(sequence);
Console.WriteLine(total); // 15
이러한 업데이트의 일부로 기본 매개 변수가 있는 메서드 그룹이 람다 식에 할당되는 경우 해당 람다 식에도 동일한 기본 매개 변수가 있습니다.
params
컬렉션 매개 변수가 있는 메서드 그룹을 람다 식에 할당할 수도 있습니다.
기본 매개 변수 또는 params
컬렉션을 매개 변수로 사용하는 람다 식에는 Func<>
또는 Action<>
형식에 해당하는 자연 형식이 없습니다. 그러나 기본 매개 변수 값을 포함하는 대리자 형식을 정의할 수 있습니다.
delegate int IncrementByDelegate(int source, int increment = 1);
delegate int SumDelegate(params int[] values);
delegate int SumCollectionDelegate(params IEnumerable<int> values);
또는 var
선언과 함께 암시적으로 형식화된 변수를 사용하여 대리자 형식을 정의할 수 있습니다. 컴파일러는 올바른 대리자 형식을 합성합니다.
람다 식의 기본 매개 변수에 대한 자세한 내용은람다 식의
비동기 람다
async 및 await 키워드를 사용하여 비동기 처리를 통합하는 람다 식과 문을 쉽게 만들 수 있습니다. 예를 들어 다음 Windows Forms 예제에는 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);
}
}
비동기 람다를 사용하여 동일한 이벤트 처리기를 추가할 수 있습니다. 이 처리기를 추가하려면 다음 예제와 같이 람다 매개 변수 목록 앞에 async
한정자를 추가합니다.
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)를 이용한 비동기 프로그래밍을 참조하세요.
람다 식 및 튜플
C# 언어는
구성 요소의 쉼표로 구분된 목록을 괄호로 묶어 튜플을 정의합니다. 다음 예제에서는 세 개의 구성 요소가 있는 튜플을 사용하여 숫자 시퀀스를 람다 식에 전달합니다. 이 식은 각 값을 두 배로 하고 곱하기 결과를 포함하는 세 개의 구성 요소가 포함된 튜플을 반환합니다.
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
등입니다. 그러나 다음 예제와 같이 명명된 구성 요소를 사용하여 튜플을 정의할 수 있습니다.
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# 튜플에 대한 자세한 내용은 튜플 유형를 참조하세요.
표준 쿼리 연산자를 사용하는 람다
LINQ to Objects에는 여러 구현 중에서 입력 매개변수의 형식이 Func<TResult> 제네릭 대리자 패밀리에 속하는 경우가 있습니다. 이러한 대리자는 형식 매개 변수를 사용하여 입력 매개 변수의 수와 형식 및 대리자의 반환 형식을 정의합니다.
Func
대리자는 원본 데이터 집합의 각 요소에 적용되는 사용자 정의 식을 캡슐화하는 데 유용합니다. 예를 들어 Func<T,TResult> 대리자 형식을 고려합니다.
public delegate TResult Func<in T, out TResult>(T arg)
Func<int, bool>
인스턴스로 대리자를 인스턴스화하여 int
을 입력 매개변수로, bool
를 반환 값으로 사용할 수 있습니다. 반환 값은 항상 마지막 형식 매개 변수에 지정됩니다. 예를 들어, Func<int, string, bool>
는 int
과 string
라는 두 개의 입력 매개 변수와 bool
라는 반환 형식을 가진 대리자를 정의합니다. 다음 Func
대리자는 호출될 때 입력 매개 변수가 5와 같은지 여부를 나타내는 부울 값을 반환합니다.
Func<int, bool> equalsFive = x => x == 5;
bool result = equalsFive(4);
Console.WriteLine(result); // False
인수 형식이 Expression<TDelegate>경우(예: Queryable 형식에 정의된 표준 쿼리 연산자) 람다 식을 제공할 수도 있습니다. Expression<TDelegate> 인수를 지정하면 람다가 식 트리로 컴파일됩니다.
다음 예제에서는 Count 표준 쿼리 연산자를 사용합니다.
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)}");
컴파일러는 입력 매개 변수의 형식을 유추하거나 명시적으로 지정할 수도 있습니다. 이 특정 람다 식은 2로 나눈 경우 나머지가 1인 정수(n
)를 계산합니다.
다음 예제는 numbers
배열에서 9 이전의 모든 요소를 포함하는 시퀀스를 생성합니다. 여기서 9는 조건에 맞지 않는 시퀀스의 첫 번째 숫자입니다.
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
다음 예제에서는 여러 입력 매개 변수를 괄호로 묶어 지정합니다. 이 메서드는 값이 배열의 서수 위치보다 작은 숫자를 찾을 때까지 numbers
배열의 모든 요소를 반환합니다.
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
var numberSets = new List<int[]>
{
new[] { 1, 2, 3, 4, 5 },
new[] { 0, 0, 0 },
new[] { 9, 8 },
new[] { 1, 0, 1, 0, 1, 0, 1, 0 }
};
var setsWithManyPositives =
from numberSet in numberSets
where numberSet.Count(n => n > 0) > 3
select numberSet;
foreach (var numberSet in setsWithManyPositives)
{
Console.WriteLine(string.Join(" ", numberSet));
}
// Output:
// 1 2 3 4 5
// 1 0 1 0 1 0 1 0
람다 표현식의 형식 유추
람다를 작성할 때 컴파일러가 C# 언어 사양에 설명된 대로 람다 본문, 매개 변수 형식 및 기타 요소를 기반으로 형식을 유추할 수 있으므로 입력 매개 변수의 형식을 지정할 필요가 없는 경우가 많습니다. 대부분의 표준 쿼리 연산자의 경우 첫 번째 입력은 소스 시퀀스의 요소 형식입니다.
IEnumerable<Customer>
쿼리하는 경우 입력 변수가 Customer
개체로 유추됩니다. 즉, 해당 메서드 및 속성에 액세스할 수 있습니다.
customers.Where(c => c.City == "London");
람다에 대한 형식 유추에 대한 일반적인 규칙은 다음과 같습니다.
- 람다는 대리자 형식과 동일한 수의 매개 변수를 포함해야 합니다.
- 람다의 각 입력 매개 변수는 해당 대리자 매개 변수로 암시적으로 변환할 수 있어야 합니다.
- 람다의 반환 값(있는 경우)은 대리자의 반환 형식으로 암시적으로 변환할 수 있어야 합니다.
람다 식의 자연 형식
공용 형식 시스템에는 "람다 식"이라는 기본 개념이 없으므로 람다 식 자체에는 형식이 없습니다. 그러나 때로는 람다 식의 "형식"을 비공식적으로 말하는 것이 편리합니다. 비공식적인 "type"은 람다 식이 변환되는 대리자 형식 또는 Expression 형식을 의미합니다.
람다 식은 자연 형식가질 수 있습니다. 람다 식에 대한 Func<...>
또는 Action<...>
같은 대리자 형식을 강제로 선언하도록 강제하는 대신 컴파일러는 람다 식에서 대리자 형식을 유추할 수 있습니다. 예를 들어 다음 선언을 고려합니다.
var parse = (string s) => int.Parse(s);
컴파일러는 parse
가 Func<string, int>
으로 유추될 수 있습니다. 컴파일러는 적합한 대리자가 있는 경우 사용 가능한 Func
또는 Action
대리자를 선택합니다. 그렇지 않으면 대리자 형식을 합성합니다. 예를 들어 대리자 형식은 람다 식에 ref
매개 변수가 있는 경우 합성됩니다. 람다 식에 자연 형식이 있는 경우 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);
명시적 반환 형식
일반적으로 람다 식의 반환 형식은 명백하고 유추됩니다. 작동하지 않는 몇몇 표현들:
var choose = (bool b) => b ? 1 : "two"; // ERROR: Can't infer return type
입력 매개 변수 앞에 람다 식의 반환 형식을 지정할 수 있습니다. 명시적 반환 형식을 지정하는 경우 입력 매개 변수를 괄호로 묶어야 합니다.
var choose = object (bool b) => b ? 1 : "two"; // Func<bool, object>
특성
람다 식 및 해당 매개 변수에 특성을 추가할 수 있습니다. 다음 예제에서는 람다 식에 특성을 추가하는 방법을 보여줍니다.
Func<string?, int?> parse = [ProvidesNullCheck] (s) => (s is not null) ? int.Parse(s) : null;
다음 예제와 같이 입력 매개 변수 또는 반환 값에 특성을 추가할 수도 있습니다.
var concat = ([DisallowNull] string a, [DisallowNull] string b) => a + b;
var inc = [return: NotNullIfNotNull(nameof(s))] (int? s) => s.HasValue ? s++ : null;
앞의 예제에서 볼 수 있듯이 람다 식 또는 해당 매개 변수에 특성을 추가할 때 입력 매개 변수를 괄호로 해야합니다.
중요하다
람다 식은 기본 대리자 형식을 통해 호출됩니다. 메서드 및 로컬 함수와 다릅니다. 대리자의 Invoke
메서드는 람다 식의 특성을 확인하지 않습니다. 특성은 람다 식이 호출될 때 아무런 영향을 주지 않습니다. 람다 식의 특성은 코드 분석에 유용하며 리플렉션을 통해 검색할 수 있습니다. 이 결정의 한 가지 결과는 System.Diagnostics.ConditionalAttribute 람다 식에 적용할 수 없다는 것입니다.
람다 식에서 외부 변수 및 변수 범위 캡처
람다는 외부 변수를 참조할 수 있습니다. 이러한
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
}
다음 규칙은 람다 식의 변수 범위에 적용됩니다.
- 캡처된 변수는 참조하는 대리자가 가비지 수집에 적합해질 때까지 가비지 수집되지 않습니다.
- 람다 식 내에 도입된 변수는 바깥쪽 메서드에 표시되지 않습니다.
- 람다 식은 바깥쪽 메서드의 ,, 또는 ref, out 매개 변수를 직접 캡처할 수 없습니다.
- 람다 식의 반환 문을 사용하면 바깥쪽 메서드가 반환되지 않습니다.
- 람다 식은 해당 jump 문의 대상이 람다 식 블록 외부에 있는 경우, goto, break, 또는 continue 문을 포함할 수 없습니다. 또한 대상이 블록 내에 있는 경우 람다 식 블록 외부에 jump 문을 두는 것도 오류입니다.
람다 식에 static
한정자를 적용하여 람다에서 지역 변수 또는 인스턴스 상태를 의도하지 않게 캡처하지 않도록 할 수 있습니다.
Func<double, double> square = static x => x * x;
정적 람다는 묶은 범위에서 지역 변수 또는 인스턴스 상태를 캡처할 수 없지만 정적 멤버 및 상수 정의를 참조할 수 있습니다.
C# 언어 사양
자세한 내용은 C# 언어 사양Anonymous 함수 식 섹션을 참조하세요.
이러한 기능에 대한 자세한 내용은 다음 기능 제안 정보를 참조하세요.
- 람다 매개 변수를 삭제합니다
- 정적 익명 함수
- 람다 개선 사항
또한 참조
- 람다(스타일 규칙 IDE0039) 대신 로컬 함수를 사용합니다.
- C# 연산자 및 식
- LINQ(Language-Integrated 쿼리)
- 식 트리
- 로컬 함수와 람다 식
- LINQ 샘플 쿼리
- XQuery 샘플
- 101 LINQ 예제
.NET