[이것이 C#이다] 14. 람다식

ssu_hyun·2022년 5월 4일
2

C#

목록 보기
18/22

Key point

  • 람다식 정의
  • 문 형식의 람다식
  • Func & Action
  • 식 트리



14.1 람다식

  • 무명 함수(Anonymous Function)를 만들기 위해 사용

식 형식의 람다식(Expression Lambda)

   // 기본 형식
   (매개변수목록) =>// 1. 무명 함수 생성 by 람다식
   delegate int Calculate(int a, int b);  // 대리자 : 반환형식 + 매개변수 정의 -> 무명함수의 모습 결정
   //...
   static void Main(string[] args)
   {
      Calculate calc = (int a, int b) => a+b; // 무명함수 by 람다식
   }
   
   
   // 2. 형식 유추(Type Inference)를 통한 간결화
   delegate int Calculate(int a, int b);  // 대리자 : 반환형식 + 매개변수 정의
   //...
   static void Main(string[] args)
   {
      Calculate calc = (a, b) => a+b; // 형식 생략
      // C# 컴파일러가 Calculator 대리자의 선언 코드로부터 익명 메소드의 매개변수 형식 유추
   }
   
   
   // 3. 무명 함수 생성 by 대리자
   delegate int Calculate(int a, int b);
   //...
   static void Main(string[] args)
   {
   	  // 람다식과 달리 길게 표현
      Calculate calc = delegate(int a, int b)
                       {
                           return a+b;
                       };
   }

예제

using System;

namespace SimpleLambda
{
    class MainApp
    {
        delegate int Calculate(int a, int b);  // 대리자 : 반환형식 + 매개변수 정의

        static void Main(string[] args)
        {
            Calculate calc = (a, b) => a + b;  // 람다식 + 형식 유추(형식 생략)

            Console.WriteLine($"{3} + {4} : {calc(3, 4)}");
        }
    }
}



문 형식의 람다식 (Statement Lambda)

  • 람다식 바디를 식이 아닌 코드 블록으로 작성
   // 기본 형식 : 화살표 오른쪽에 {코드 블록} 
   (매개변수_목록) => {
   					  문장1;
                      문장2;
                      문장3;
                      ...
                    }
	
    // 반환 형식이 없는 무명 함수 생성 가능
    delegate void DoSomething();  // 대리자
    //...
    static void Main(string[] args)
    {
       DoSomething DoIt = () =>  // 문 형식의 람다식 (반환값X)
               {
                  Console.WriteLind("뭔가를");
                  Console.WriteLind("출력해보자.");
                  Console.WriteLind("이렇게!");
               };
               
       DoIt();
    }

예제

using System;

namespace StatementLambda
{
   class MainApp
   {
       delegate string Concatenate( string[] args );  // 대리자

       static void Main(string[] args)
       {
           Concatenate concat =  // 문 형식의 람다식
               ( arr ) =>
               {
                   string result = "";
                   foreach (string s in arr)
                       result += s;

                   return result;
               };

           Console.WriteLine( concat(args) );
       }
   }
}

string[] args

  • C# 프로그램 (exe 실행 파일)을 콘솔 윈도우(cmd.exe)에서 실행할 때 실행 코드 뒤에 작성한 옵션들(Command-Line Argument)은 args에 전달된다.
  • args는 문자열 배열로 공백으로 구분된 옵션들이 차례대로 배열에 저장된다.
    • Command-Line Argument : 고양이 우주 나비
      => args[0]=고양이, args[1]=우주, args[2]=나비
    • 만약 하나의 옵션 내 공백이 있을 경우 ""로 묶어 준다.

14.2 Func 대리자 & Action 대리자

  • 익명 메소드/무명 함수 정의를 위해 매번 대리자를 선언해야하는 불편 제거
  • .NET 라이브러리에 미리 선언되어 있는 대리자
    • Func 대리자 : 반환 값이 있는 익명 메소드/무명 함수용 대리자
    • Action 대리자 : 반환 값이 없는 익명 메소드/무명 함수용 대리자

Func 대리자

  • 결과를 반환하는 메소드를 참조하기 위해 생성
    • 모든 Func 대리자의 형식 매개변수에서 마지막은 무조건 반환 형식
  • 17가지 버전
    • 입력 매개변수가 16개 이상이던지, ref나 out 한정자로 수식된 매개변수를 사용해야 하는 경우가 아니면 변도의 대리자를 만들어 쓸 필요가 없다.

예제

using System;

namespace FuncTest
{
    class MainApp
    {
        static void Main(string[] args)
        {
            // Func<TResult> : 입력 매개변수 없는 버전
            Func<int> func1 = () => 10; // 입력 매개변수는 없으며, 무조건 10 반환
            Console.WriteLine($"func1() : {func1()}");

            // Func<T1, TResult> : 매개변수 1개
            Func<int, int> func2 = (x) => x * 2;  // 입력 매개변수는 int 형식 하나, 반환 형식도 int
            Console.WriteLine($"func2(4) : {func2(4)}"); 

            // Func<T1, T2, TResult> : 매개변수 2개
            Func<double, double, double> func3 = (x, y) => x / y; // 입력 매개변수는 double 형식 둘, 반환 형식도 double
            Console.WriteLine($"func3(22, 7) : {func3(22, 7)}");
        }
    }
}


Action 대리자

  • 반환 형식이 없고 입력 매개변수를 위해 선언
    • 결과 반환보다 일련의 작업을 수행하는 것이 목적

예제

using System;

namespace ActionTest
{
    class MainApp
    {
        static void Main(string[] args)
        {
        	// Action<T> : 매개변수 1개 버전
            Action act1 = () => Console.WriteLine("Action()");            
            act1();

            int result = 0;
            Action<int> act2 = (x) => result = x * x;  // result에 x * x 결과 저장

            act2(3);
            Console.WriteLine($"result : {result}");
			
            
            // Action<T1, T2> : 매개변수 2개 버전
            Action<double, double> act3 = (x, y) =>
                {
                    double pi = x / y;
                    Console.WriteLine($"Action<T1, T2>({x}, {y}) : {pi}");
                };
            
            act3(22.0, 7.0); 
        }
    }
}



14.3 식 트리(Expression Tree)

  • 식을 트리로 표현한 자료 구조

    • 실행가능한 상태가 아닌 "데이터" 상태이므로 실행을 위해선 컴파일 과정 필요 (Expression<TDelegate> 클래스 이용)
  • 노드(Node, 마디)로 구성

    • 루트(Root) : 트리의 뿌리
    • 잎(Leaf)/단말(Terminal) : 노드 중 가장 끝에 있는 노드
    • 부모-자식 관계
      • 한 부모가 단 두 개만의 자식 노드를 가질 수 있는 이진 트리(Binary Tree)
      • 연산자=부모 노드, 피연산자=자식 노드
  • 컴파일러나 인터프리터 제작에 응용

    • 컴파일러는 소스 코드를 분석해 식 트리로 만든 후 이를 바탕으로 실행 파일 생성
    • C#은 프로그래머가 코드 안에서 직접 식 트리를 조립하고 컴파일해서 사용할 수 있는 동적 무명 함수 생성 기능 제공
  • Expression 클래스 (System.Linq.Expression)

    • 노드 표현
    • 파생 클래스들의 객체 생성 역할(팩토리 메소드)

      팩토리 메소드(Factory Method)

      • 클래스의 인스턴스 생성을 담당하는 메소드
      • 객체 생성 과정을 별도의 메소드에 구현해놓으면 코드의 복잡도를 상당히 줄일 수 있다.
      • Expression 클래스의 정적 팩토리 메소드들은 파생 클래스들의 인스턴스를 생성하는 기능을 제공함으로써 편리성 제공
        // Expression 클래스의 팩토리 메소드를 통해 객체 생성
        Expression const1 = Expression.Constant(1);  // 상수 1 표현 객체
        Expression param1 = Expression.Parameter(typeof(int), "x");  // 매개변수 x 표현 객체
        Expression exp = Expression.Add(const1, param1);  // 덧셈 연산 수행 객체
        ※ Expression을 상속하므로 ConstantExpression이 아닌
        Expression 형식으로 선언하여도 참조를 통해 가리킬 수 있다.
  • 코드를 데이터로써 보관

    • 파일에 저장
    • 네트워크를 통해 다른 프로세스에 전달
    • 코드를 담고 있는 식 트리 데이터를 데이터베이스 서버에 보내 실행 (LINQ, Language INtegrated Query)

예제

// 1. 식 트리 by Expression<TDelegate> //

using System;
using System.Linq.Expressions;  // Expression 클래스

namespace UsingExpressionTree
{
    class MainApp
    {
        static void Main(string[] args)
        {
            // 1*2+(x-y) 구현 //
            
            Expression const1 = Expression.Constant(1);  // 상수 1
            Expression const2 = Expression.Constant(2);  // 상수 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); // 곱 연산 + 빼기 연산
			
            // Expression <TDelegate>로 식 트리 생성
            Expression<Func<int, int, int>> expression =
                Expression<Func<int, int, int>>.Lambda<Func<int, int, int>>(
                    exp, new ParameterExpression[]{
                        (ParameterExpression)param1, 
                        (ParameterExpression)param2});
			
            Func<int, int, int> func = expression.Compile();  // 실행가능한 코드로 컴파일
            
            // x = 7, y = 8
            Console.WriteLine($"1*2+({7}-{8}) = {func(7,8)}");  // 컴파일한 무명 함수 실행         
        }
    }
}

// 2. 식 트리 by 람다식 //
// 이 경우 Expression 형식은 불변이므로 "동적으로" 식 트리를 만들기 어렵지만 
// 더 간편하게 만들 수 있다.

using System;
using System.Linq.Expressions;

namespace ExpressionTreeViaLambda
{
    class MainApp
    {
        static void Main(string[] args)
        {
            Expression<Func<int, int, int>> expression =
                (a, b) => 1*2+(a-b);
            Func<int, int, int> func = expression.Compile();
            
            // x = 7, y = 8
            Console.WriteLine($"1*2+({7}-{8}) = {func(7,8)}");  // 컴파일한 무명 함수 실행         
        }
    }
}



14.4 식 본문 멤버(Expression-Bodied Member)

  • 멤버의 본문을 식(Expression)만으로 구현한 것
   멤버 =>;

예제

using System;
using System.Collections.Generic;

namespace ExpressionBodiedMember
{
    class FriendList  // Friend 클래스 선언
    {
        private List<string> list = new List<string>();  // 이름 넣을 리스트 선언
        
        public void Add(string name) => list.Add(name);  // 이름 추가
        public void Remove(string name) => list.Remove(name);  // 이름 삭제
        public void PrintAll()  // 리스트에 있는 모든 이름 출력
        {
            foreach (var s in list)
                Console.WriteLine(s);
        }
        
        public FriendList() => Console.WriteLine("FriendList()");  // 생성자
        ~FriendList() => Console.WriteLine("~FriendList()");  // 종료자
        
        // public int Capacity => list.Capacity;  // 읽기 전용 속성
        
        public int Capacity // 읽기/쓰기 모두 가능한 속성
        {
            get => list.Capacity;
            set => list.Capacity = value;
        }
        
        // public string this[int index] => list[index];  // 읽기 전용 인덱서
        public string this[int index]  // 읽기/쓰기 모두 가능한 인덱서
        {
            get => list[index];
            set => list[index] = value;
        }
    }
    
    class MainApp
    {
        static void Main(string[] args)
        {
            FriendList obj = new FriendList();
            obj.Add("Eeny");
            obj.Add("Meeny");
            obj.Add("Miny");
            obj.Remove("Eeny");
            obj.PrintAll();
            
            Console.WriteLine($"{obj.Capacity}");
            obj.Capacity = 10;
            Console.WriteLine($"{obj.Capacity}");
            
            Console.WriteLine($"{obj[0]}");
            obj[0] = "Moe";
            obj.PrintAll();
        }
    }
}



연습 문제

  1. 다음 코드의 출력 결과값은 얼마일까요? => 70
Func<int> func_1 = () => 10; 
Func<int, int> func_2 = (a) => a*2;  
Console. WriteLine(func_1() + func_2(30));  // 10 + 30*2 = 70
  1. 다음 코드에서 익명 메소드를 람다식으로 수정하세요.
// 기본 코드
using System;

namespace _14_2
{
    class MainApp
    {
        static void Main(string[] args)
        {
            int[] array = { 11, 22, 33, 44, 55 };
            

            foreach (int a in array)
            {
                Action action = new Action
                    (
                        delegate()
                        {
                            Console.WriteLine(a*a);
                        }
                    );
             }
         }
     }
 }


// 람다식으로 수정
using System;

namespace _14_2
{
    class MainApp
    {
        static void Main(string[] args)
        {
            int[] array = { 11, 22, 33, 44, 55 };
            

            foreach (int a in array)
            {
                int result = 0;
                Action<int> act1 = (a) => result = a * a;
                act1(a);
                Console.WriteLine($"a = {a} => result = {result}");
             }
         }
     }
 }

     

0개의 댓글