Programming/.NET

람다식은 수학에서의 람다대수형식을 C#언어의 문법으로 표현한것입니다. 람다식을 사용하면 기존의 길고 복잡했던 구문을 간소화하고 단순화시킬 수 있습니다.

 

예를 들어 익명메서드를 사용한 하나의 예를

 

delegate int callMethod(int iint j);

static void Main(string[] args)
{
    callMethod cm = delegate (int iint b)
    {
        return i + b;
    };

    Console.Read();
}

 

람다식으로 표현하면 다음과 같이 할 수 있습니다.

 

delegate int callMethod(int iint j);

static void Main(string[] args)
{
    callMethod cm = (ib) => i + b;

    Console.Read();
}

 

우선 callMethod 타입에서는 델리게이트 선언으로 인해 델리게이트형식의 익명메서드, 그리고 해당 메서드에 int형 매개변수 2가 전달된다는 것을 알 수 있습니다. 따라서 deleteage와 int형식은 다음과 같이 생략할 수 있습니다.

 

delegate (int i, int b) -> (i, b)

 

그리고 이 생략된 표현이 람다식임을 => 문자로 알려줍니다.

 

람다식 표현에서 반환값이 없는 경우에는 중괄호({})가 있어야 하지만 반환값이 존재하는 경우에는 return과 해당 부분을 감싸는 중괄호가 생략될 수 있기에 최종적으로 다음과 같은 표현이 완성됩니다.

 

(i, b) => i + b;

 

callMethod cm = (ib) => i + b;
Console.WriteLine(cm(100, 200));

 

예제에서는 람다식으로 델리게이트를 사용해 익명 메서드를 구현하고 있습니다. 이것은 일반적인 익명 메서드도 마찬가지지만 람다식으로 익명 메서드를 구현하기 위해서는 일일이 델리게이트를 선언해야 한다는 것을 의미합니다. 이런 불편함을 감소시키고자 .NET에서는 델리게이트의 일부 형식을 구현한 Action과 Func를 마련해 두었습니다.

 

static void Main(string[] args)
{
    Action<int> a_i = (i) => { Console.WriteLine("number : {0}"i)};
    a_i(100);

    Console.Read();
}

 

먼저 Action은 반환값이 없는 익명메서드를 구현하는데 사용됩니다. Action에 보시면 매개변수로 받을 수 있는 데이터형식을 <int>로 정의했는데 이것은 Action<T>로 제네릭화 되어있다는 것을 알 수 있습니다. 이런 사항은 Func도 마찬가지입니다.

 

Func<intintint> cal = (ij) => i + j;
int sum = cal(100, 200);
Console.WriteLine(sum);

 

Func는 반환값이 있는 경우에 사용할 수 있습니다. 예제에서 Func는 매개변수 데이터 형식으로 int형 3개가 존재하는데 처음 2개는 i와 j에 대응되고 나머지 하나는 반환형식에 해당합니다.

 

Action과 Func사용시 매개변수는 최대 16개까지 유동적으로 사용할 수 있습니다.

 

람다식은 익명메서드를 구현하는것 뿐만 아니라 컬렉션에도 적용할 수 있습니다. 예를 들어

 

List<int> l = new List<int{ 1, 2, 3, 4, 5 };

 

위와 같은 List의 경우 각 요소를 순회하려면 다음과 같이 할 수 있지만

 

foreach (int i in l)
    Console.WriteLine(i);

 

Action 람다식을 적용하면

 

l.ForEach((el) => { Console.WriteLine(el)});

 

이와 같이 처리할 수 있습니다. 예제를 보면 컬렉션의 ForEach메서드를 사용하는데 이 메서드는 Action<T>타입을 받으므로 람다식의 Action을 전달하면 됩니다.  다음 예제는 ForEach의 또 다른 처리방식을 보여주고 있습니다.

 

Array.ForEach(l.ToArray()(el) => { Console.WriteLine(el)});

 

Action은 리턴값을 가지지 않는다고 하였습니다. 따라서 특정 조건에 해당하는 요소만을 반환받으려면 Func를 사용할 수 있습니다.

 

List<int> r = l.FindAll((el) => el > 3);
r.ForEach((el) => { Console.WriteLine(el)});

 

이때 메서드는 FindAll을 사용하며 Func를 구현하는 람다식을 통해 3보다 큰 값만 가져오도록 하였습니다. FindAll과 비슷한 메서드로 Where도 존재하는데

 

IEnumerable<int> r = l.Where((el) => el > 3);
Array.ForEach(r.ToArray()(el) => { Console.WriteLine(el)});

 

이때 Where는 List가 아닌 IEnumerable형식으로 결과를 반환합니다. IEnumerable은 자체적인 ForEach메서드를 지원하지 않으므로 Array의 ForEach를 호출해 결과요소를 반환받아야 합니다. 이때 만약 반환되는 데이터 타입을 달리해야 한다면 Select를 사용할 수 있습니다.

 

IEnumerable<double> r = l.Select((el) => double.Parse((el > 3).ToString()));

 

FindAll과 Where메서드는 결과는 같지만 동작방식은 약간 다른데 Where메서드는 호출되는 즉시 동작하지 않고 ForEach로 배열순회가 되서야 실행이 됩니다. 이를 지연된 평가라고 하는데 이때문에 요소를 임의적으로 정리만 해준다면 Where에서 다루어지는 요소가 전체가 아닌 일부만으로 처리될 수 있기에 성능적으로 도움이 될 수 있습니다.

 

위 예제에서 사용된 Select도 마찬가지인데 이는 ConvertAll에서 지연된 평가를 위해 만들어진 메서드입니다. 이처럼 람다식자체는 메서드나 컬렉션에서 특정 연산을 위해 사용될 수 있습니다.

 

이 외에도 람다식은 식자체를 데이터로서 취급하는 방법이 존재합니다. 예를 들어 Func에 관한 람다식을 데이터로 하려면

 

Expression<Func<intintint>> exp = (ij) => i + j;

 

위와 같이 할 수 있습니다. Expression<T>에서 T는 식에 해당하는 델리게이트타입이 전달되며 exp가 식을 담고 있는 변수에 해당합니다. 따라서 Expression 개체는 다음 형태처럼 구문에 대한 개별적인 표현이 가능합니다.

 

Expression<Func<intintint>> exp = (ij) => i + j;

BinaryExpression be = (BinaryExpression)exp.Body;
Console.WriteLine(be.ToString());

 

exp에서 Body는 식의 본체를 의미하고 이를 출력해 보면 (i + j)라는 내용을 확인할 수 있습니다. 그런데 반대의 경우도 가능해서 Expression을 통해 식을 구성할 수도 있습니다.

 

ParameterExpression Lpe = Expression.Parameter(typeof(int)"i");
ParameterExpression Rpe = Expression.Parameter(typeof(int)"j");

BinaryExpression plusBe = Expression.Add(Lpe, Rpe);

Expression<Func<intintint>> exp = Expression<Func<intintint>>.Lambda<Func<intintint>>(plusBe, new ParameterExpression[] { Lpe, Rpe });

 

좌항과 우항을 ParameterExprsssion으로 구성하고 BinaryExpression을 통해 2항 연산에 관한 식을 정의합니다. 이때 Expression의 Add 메서드를 통해 +연산자를 적용한뒤 연산자와 각항을 구성하고 Lamda메서드로 전체식을 구성합니다. 물론 BinaryExpression외에도 루프를 표현하는 LoopExpression이나 상수식을 표현하는 ConstantExpression등 구성할 수 있는 다양한 타입의 식이 존재합니다.

 

Console.WriteLine(exp.ToString());
Func<intintint> f = exp.Compile();
Console.WriteLine(f(10, 20));

 

위에서 처럼 식을 데이터로서 취급하면서도 컴파일과정을 통해 실제 실행가능한 코드로 동작시킬 수도 있습니다.

 

Expression<Func<intintint>> exp = (ij) => i + j;

Func<intintint> f = exp.Compile();
Console.WriteLine(f(10, 20));

 

식을 데이터로서 다루고자 하는 이러한 방법은 프로그램이 동작하는 시점에 필요한 처리로직을 동적으로 생성하고 실행할 수 있다는 것인데 사실 잘 쓰이지는 않는 방법입니다. 그냥 이런게 가능하다는 것만 알아두고 넘어가도 좋을듯 합니다.

 

참고로 람다식을 이용하면 익명메서드와 비슷하게 단일문의 메서드를 생성할 수 있습니다.

 

static void Main(string[] args)
{
    int sum = plus(10, 20);

    Console.Read();
}

static int plus(int iint j) => (i + j);

 

plus메서드는 2개의 매개변수값을 받아 하나의 결과를 반환하는 단일문으로 메서드가 구현되었습니다.

0 0