전체 페이지뷰

2017년 1월 3일 화요일

C# Lambda Expression

람다는 주로 delegate나 Expression tree(식 트리)를 만드는데에 사용됩니다.

매개변수 => 식
의 문법으로 사용가능하며

delegate int del(int i); 
del myDelegate = x => x * x;  
cs
와 같이 사용하여 제곱을 표현하는 델리게이트 인스턴스를 생성했습니다.



위의 예는 매개변수가 하나인 경우이며, 두개 이상인 경우에는 매개변수를
(int x, int y) => x+y
와 같이 ()로 묶어줘야 하며, 매개변수가 없는 경우에는 빈 ()로 표현합니다.

위의 델리게이트를 앞서 살펴봤던 익명메소드로 표현하면 어떻게 될까요?
delegate int del(int i);
del myDelegate = delegate (int x)
{
     return x * x;
};
cs

보시다시피 람다 쪽이 훨씬 간결합니다. 


문(Statement) 형식의 람다


(매개변수목록) => { //...; }
의 형식으로 {} 사이에 문장이 들어오는 형식의 람다입니다.

using System;
namespace StatementLambda
{
    class Program
    {
        delegate void TestDelegate(string s);
        static void Main(string[] args)
        {
            TestDelegate test = (s) =>
            {
                string testS = s + " " + "World";
                Console.WriteLine(testS);
            };
            test("Hello");
        }
    } 
}
cs


Func와 Action


지금까지 람다를 포함한 모든 익명메소드의 작성시 반드시 해당하는 델리게이트가 필요했습니다. 이런 번거로움을 피하기 위해 .NET 프레임워크 내에 이미 Func와 Action이라는 델리게이트가 선언되어 있습니다. Func는 결과를 반환하는 메소드, Action은 결과를 반환하지 않는 메소드를 위한 델리게이트입니다.


Func

기본적으로 매개변수가 16개까지(0~16 까지 총 17개) 델리게이트가 정의되어 있어 어지간해선 새로 만들 필요없이 사용 가능합니다.

매개변수 없을 때: Func<TResult>
Func<int> func1 = () => 10;  //매개변수 없이 10 return
Console.WriteLine(func1());   // 10출력


매개변수 1개: Func<T1, Tresult>
Func<int, int> func2 = (x) => x*x;
Console.WriteLine(func2(3));  //9출력

매개변수 2개: Func<T1, T2, Tresult>
Func<int, int, int> func3 = (x, y) => x + y;
Console.WriteLine(func3(2,3));  // 5출력

using System;
namespace FuncTest
{
    class Program
    {       
        static void Main(string[] args)
        {
            Func<int> func1 = () => 10;
            Console.WriteLine(func1());
            Func<intint> func2 = (x) => x * 2;
            Console.WriteLine(func2(3));
            Func<doubledoubledouble> func3 = (x, y) => x / y;
            Console.WriteLine(func3(22,7));
        }
    } 
}
cs


Action


Func와 다른 것은 반환형식이 없다는 것 뿐입니다. 반환값이 없으므로 마지막 TResult에 해당하는 부분이 필요치 않습니다. 따라서 Func보다 매개변수가 하나 적습니다.

매개변수 없을 때:
Action act1 = () => Console.WriteLine("Action()");
act1();

매개변수 1개: Action<T>
int result = 0;
Action<int> act2 = (x) => result = x * x;
act2(3);
Console.WriteLine(result);

매개변수 2개: Action<T1,T2>
Action<double, double> act3 = (x, y) => 
{
    double result = x / y;
    Console.WriteLine(" {0} / {1} = {2}", x, y, result);
};
act3( 4.0, 15.0);




식트리


식 트리는 노드와 에지가 나무가지 모양을 이루고 있는 자료구조입니다.(참고)

식트리에 이용되는 클래스는 System.Linq.Expressions 에 정의되어 있습니다.


클래스설명
System_CAPS_pubclassBinaryExpression
이항 연산자가 있는 식을 나타냅니다.
System_CAPS_pubclassBlockExpression
변수를 정의할 수 있는 식의 시퀀스가 포함된 블록을 나타냅니다.
System_CAPS_pubclassCatchBlock
try 블록의 catch 문을 나타냅니다.
System_CAPS_pubclassConditionalExpression
조건부 연산자가 있는 식을 나타냅니다.
System_CAPS_pubclassConstantExpression
상수 값이 있는 식을 나타냅니다.
System_CAPS_pubclassDebugInfoExpression
디버그 정보에 대한 시퀀스 위치를 내보내거나 지웁니다. 그러면 디버거에서 디버깅할 때 올바른 소스 코드를 강조 표시할 수 있습니다.
System_CAPS_pubclassDefaultExpression
형식 또는 빈 식의 기본값을 나타냅니다.
System_CAPS_pubclassDynamicExpression
동적 연산을 나타냅니다.
System_CAPS_pubclassDynamicExpressionVisitor
동적 식 트리의 방문자 또는 재작성기를 나타냅니다.
System_CAPS_pubclassElementInit
IEnumerable 컬렉션의 단일 요소에 대한 이니셜라이저를 나타냅니다.
System_CAPS_pubclassExpression
식 트리 노드를 나타내는 클래스가 파생되는 기본 클래스를 제공합니다. 또한 다양한 노드 형식을 만드는 static(Visual Basic에서는 Shared) 팩터리 메서드가 들어 있습니다. 이 클래스는 abstract 클래스입니다.
System_CAPS_pubclassExpression<TDelegate>
강력한 형식의 람다 식을 식 트리 형태의 데이터 구조로 나타냅니다. 이 클래스는 상속될 수 없습니다.
System_CAPS_pubclassExpressionVisitor
식 트리의 방문자 또는 재작성기를 나타냅니다.
System_CAPS_pubclassGotoExpression
무조건 점프를 나타냅니다. 여기에는 return 문, break 문, continue 문 및 기타 점프가 포함됩니다.
System_CAPS_pubclassIndexExpression
속성 또는 배열의 인덱싱을 나타냅니다.
System_CAPS_pubclassInvocationExpression
인수 식 목록에 대리자 또는 람다 식을 적용하는 식을 나타냅니다.
System_CAPS_pubclassLabelExpression
에 배치할 수 있는 레이블을 나타냅니다 Expression 컨텍스트. 해당 제공한 값을 가져오는에 레이블로 점프, GotoExpression합니다. 그렇지 않으면 값을 받는 DefaultValue합니다. 하는 경우는 Type System.Void, 값을 제공 해야 합니다.
System_CAPS_pubclassLabelTarget
대상을 나타내는 데 사용 되는 GotoExpression합니다.
System_CAPS_pubclassLambdaExpression
람다 식을 설명합니다. .NET 메서드 본문과 유사한 코드 블록을 캡처합니다.
System_CAPS_pubclassListInitExpression
컬렉션 이니셜라이저가 있는 생성자 호출을 나타냅니다.
System_CAPS_pubclassLoopExpression
무한 루프를 나타냅니다. "break"로 종료할 수 있습니다.
System_CAPS_pubclassMemberAssignment
필드 또는 개체의 속성에 대한 할당 작업을 나타냅니다.
System_CAPS_pubclassMemberBinding
기본 클래스를 제공하며, 이 기본 클래스에서 새로 만든 개체의 멤버를 초기화하는 데 사용되는 바인딩을 나타내는 클래스가 파생됩니다.
System_CAPS_pubclassMemberExpression
필드 또는 속성에 대한 액세스를 나타냅니다.
System_CAPS_pubclassMemberInitExpression
생성자 호출 및 하나 이상의 새 개체 멤버에 대한 초기화를 나타냅니다.
System_CAPS_pubclassMemberListBinding
새로 만든 개체의 컬렉션 멤버 요소에 대한 초기화를 나타냅니다.
System_CAPS_pubclassMemberMemberBinding
새로 만든 개체 멤버의 멤버에 대한 초기화를 나타냅니다.
System_CAPS_pubclassMethodCallExpression
정적 메서드 또는 인스턴스 메서드에 대한 호출을 나타냅니다.
System_CAPS_pubclassNewArrayExpression
새 배열 생성 및 해당 새 배열의 요소 초기화를 나타냅니다.
System_CAPS_pubclassNewExpression
생성자 호출을 나타냅니다.
System_CAPS_pubclassParameterExpression
명명된 매개 변수 식을 나타냅니다.
System_CAPS_pubclassRuntimeVariablesExpression
변수에 대 한 런타임 읽기/쓰기 권한을 제공 하는 식입니다.
System_CAPS_pubclassSwitchCase
한 사례는 SwitchExpression합니다.
System_CAPS_pubclassSwitchExpression
컨트롤을 전달 하 여 여러 선택 항목을 처리 하는 제어 식을 나타냅니다 SwitchCase합니다.
System_CAPS_pubclassSymbolDocumentInfo
소스 파일에 대한 디버깅 기호 정보를 내보내는 데 필요한 정보 특히, 파일 이름 및 고유 언어 식별자를 저장합니다.
System_CAPS_pubclassTryExpression
try/catch/finally/fault 블록을 나타냅니다.
System_CAPS_pubclassTypeBinaryExpression
식과 형식 간의 작업을 나타냅니다.
System_CAPS_pubclassUnaryExpression
단항 연산자가 있는 식을 나타냅니다.



이 기본 클래스들과 다양한 static 팩토리 메소드들을 이용하여 식트리 형태의 코드를 만드는 것이 가능합니다.

예를 들어 다음의 코드는 1+x라는 식을 트리로 표현합니다.

Expression const1 = Expression.Constant(1);  //상수1
Expression param1 = Expression.Parameter(typeof(int), "x"); //매개변수 x
Expression exp = Expression.Add( const1, param1 );  // 1+x

이렇게 해서 작성된 코드는 실행 가능한 상태가 아니고 그저 데이터 상태에 머물러 있을 뿐입니다. 이 exp는 람다식으로 컴파일 되어야 실행 가능해지는데 Expression<TDelegate> 클래스를 이용하여 람다식으로 컴파일 가능합니다.

Expression const1 = Expression.Constant(1);  //상수1
Expression param1 = Expression.Parameter(typeof(int), "x"); //매개변수 x

Expression exp = Expression.Add( const1, param1 );  // 1+x
Expression<Func<int, int>> lambda1 = 
    Expression<Func<int, int>>.Lambda<Func<int, int>>(
        exp, new ParameterExpression[]{ParameterExpression)param1});

Func<int, int> compiledExp = lambda1.Compile(); //실행 가능하게


은근히 복잡하네요. 이제 1*2+(x-y)라는 식을 식 트리로 만들어 컴파일 하는 예제를 보겠습니다.


using System;
using System.Linq.Expressions;
namespace ExpressionTree
{
    class Program
    {       
        static void Main(string[] args)
        {
            // 1*2+(x-y)
            Expression const1 = Expression.Constant(1);
            Expression const2 = Expression.Constant(2);
            Expression leftExp = Expression.Multiply(const1, const2); //1*2
            Expression param1 = Expression.Parameter(typeof(int));  // x를 위한 변수
            Expression param2 = Expression.Parameter(typeof(int)); // y를 위한 변수
            Expression rightExp = Expression.Subtract(param1, param2); // x-y
            Expression exp = Expression.Add(leftExp, rightExp); // 1*2+(x-y)
            Expression<Func<intintint>> expression =
                Expression<Func<intintint>>.Lambda<Func<intintint>>(
                    exp, new ParameterExpression[]
                    {
                        (ParameterExpression)param1,(ParameterExpression)param2
                    });
            Func<intintint> func = expression.Compile();
            // x = 7, y = 8
            Console.WriteLine("1*2+(7-8)={0}", func(78));
        }
    } 
}
cs

결과) 1*2+(7-8)=1

이것을 람다식을 이용하여 다시 만들어봅니다.


using System;
using System.Linq.Expressions;
namespace ExpressionTree
{
    class Program
    {       
        static void Main(string[] args)
        {
            Expression<Func<intintint>> expression = (a, b) => 1 * 2 + (a - b);
            Func<intintint> func = expression.Compile();
            // x = 7, y = 8
            Console.WriteLine("1*2+(7-8)={0}", func(78));
        }
    } 
}
cs

훨씬 간단하지만 람다식을 이용할 때는 동적으로 식트리를 만들기는 어렵다고 합니다.

식트리가 지금은 어디에 필요한지를 잘 모르겠지만, 이제 곧 알아볼 LINQ에 가면 그 필요성을 깨닫게 될 것입니다.

댓글 없음:

댓글 쓰기