Skip to main content

[This is C#] Chapter 14. 람다식

이것이 C#이다 책을 읽고 공부한 노트입니다.




람다식 #

  • 람다식(Lambda Expression)
    • Chapter 13에서도 보았던 익명 메소드를 만들기 위해 사용한다.
    • 림다식으로 만드는 익명 메소드는 무명 함수(Anonymouse Function) 라고 부른다.
    • C# 컴파일러는 형식 유추(Type Inference)라는 기능을 제공한다. 형식 유추를 사용하면 람다식에서 매개변수의 형식을 제거할 수 있다.
delegate int Calculate(int a, int b);

class MainApp
{
    static void Main(string[] args)
    {
        // 익명 메소드
        Calculate calc1 = delegate (int a, int b)
        {
            return a + b;
        };

        // 람다식으로 만든 무명 함수 (형식 유추로 매개변수의 형식을 제거할 수 있다.)
        Calculate calc2 = (a, b) => a + b;
    }
}



문 형식의 람다식 #

  • 문 형식의 람다 식(Statement Lambda)
    • a + b같은 식이 아니라 if (a == b) return 0; else return 1;같은 문장을 사용할 수 있는 람다 식이다.
    • =>연산자의 오른편이 { }과 같은 코드 블록을 만든다.
delegate void DoSomething();

class MainApp
{
    static void Main(string[] args)
    {
        // 문 형식의 람다 식 
        DoSomething doIt = () =>
        {
            Console.WriteLine("뭔가를");
            Console.WriteLine("출력해");
            Console.WriteLine("보자!");
        };

        doIt();
    }
}



Func와 Action으로 더 간편하게 무명 함수 만들기 #

  • .NET 안에 미리 선언되어 있는 대리자들이다.

Func 대리자 #

  • Func 대리자
    • 결과를 반환하는 메소드를 참조하기 위해 만들어졌다.
static void Main(string[] args)
{
    Func<int> func1 = () => 1;
    Func<int, int> func2 = (a) => a + 1;
    Func<int, int, int> func3 = (a, b) => a + b + 1;
}

Action 대리자 #

  • Action 대리자
    • 반환 형식이 없는 메소드를 참조하기 위해 만들어졌다.
static void Main(string[] args)
{
    Action action1 = () => Console.WriteLine("Hello");

    int result = 0;
    Action<int> action2 = (a) => result = a;

    Action<int, int> action3 = (a, b) => result = a + b;
}



식 트리 #

  • 식 트리(Expression Tree)
    • 식을 트리 구조로 표현한 것이다.
    • C#은 프로그래머가 코드로 직접 식 트리를 조립하고 컴파일해서 사용할 수 있다.
    • 식 드리를 다루는 데 필요한 클래스들은 System.Linq.Expressions 네임스페이스 안에 준비되어 있다.
      • 모두 Expression 클래스의 파생 클래스들이다.
      • Expression 클래스 자신은 abstract 클래스여서 자신의 인스턴스를 만들 수는 없지만, 파생 클래스의 인스턴스를 생성하는 정적 팩토리 메소드를 제공한다.
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 righExp = Expression.Subtract(param1, param2); // x - y

Expression exp = Expression.Add(leftExp, righExp); // (1 * 2) + (x - y)  // body


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();


Console.WriteLine($"{func(7, 8)}"); // 1

  • 람다 식을 이용하면 더 간편하게 식 트리를 만들 수 있다.
    • 다만 이 경우에는 동적으로 식 트리를 만들기는 어려워진다.
Expression<Func<int, int, int>> expression = (a, b) => (1 * 2) + (a - b);

Func<int, int, int> func = expression.Compile();

Console.WriteLine($"{func(7, 8)}"); // 1



식으로 이루어지는 멤버 #

  • 클래스의 멤버 중에서 본문이 중괄호로 만들어져 있는 것들
    • 메소드, 생성자, 종료자, 프로퍼티, 인덱서
    • 이것은 모두 본문을 식으로만 구현할 수 있다. 이것을 식 본문 멤버(Expression-Bodied Member) 라고 한다.
class MyList
{
    private List<int> list = new List<int>();

    // 읽기 전용 프로퍼티와 인덱서
    public int Capacity => list.Capacity;
    public int this[int idx] => list[idx];

    // 읽기, 쓰기 모두 가능한 프로퍼티와 인덱서
    public int Capacity
    {
        get => list.Capacity;
        set => list.Capacity = value;
    }

    public int this[int idx]
    {
        get => list[idx];
        set => list[idx] = value;
    }

    // 생성자와 소멸자
    public MyList() => Console.WriteLine("Hello");
    ~MyList() => Console.WriteLine("Bye");

    // 메서드들
    public void Add(int val) => list.Add(val);
    public void Remove(int val) => list.Remove(val);
}