달나라 노트

C# : Lambda (Event Handler에 Lambda 식 적용) 본문

C#/C#

C# : Lambda (Event Handler에 Lambda 식 적용)

CosmosProject 2022. 5. 30. 19:31
728x90
반응형

 

 

 

C#에서도 Lambda 식을 지원합니다.

 

Lambda식은 무명 함수(Anonymous Function)라고도 하는데 말 그대로 이름이 없는 함수라는 의미입니다.

 

이에 대해 좀 더 살펴봅시다.

 

 

int multiply(int x, int y)
{
    return x * y;
}

 

보통 C#에서 함수를 선언할 땐 위처럼 선언합니다.

위 함수는 2개의 인자를 받아 이 2개의 안자를 곱한 값을 return해주는 함수입니다.

 

- int multiply(int x, int y)

위 부분에서 int는 함수가 return하는 값의 data type입니다.

multiply는 제가 정한 함수의 이름입니다.

괄호 안에 (int x, int y)는 함수가 받는 parameter와 parameter의 data type을 의미합니다.

 

그리고 중괄호{} 안에 함수의 내용이 들어갑니다.

 

이렇게 기본적으로 함수를 선언할 때에는 중괄호{} 내부에 있는 함수의 기능을 담당하는 부분 뿐 아니라 함수의 이름까지 선언해야 합니다.

 

 

 

 

using System;

class Sample2
{
    public static void Main()
    {
        int multiply(int x, int y)
        {
            return x * y;
        }
        Console.WriteLine(multiply(5, 7));
    }
}


-- Result
35

 

위 코드를 봅시다.

multiply 함수를 선언하고 multiply(5, 7)의 형태로 multiply 함수를 호출하고 있습니다.

 

함수 호출을 할 때 multiply(5, 7) 이렇게 하였으므로 5 * 7의 결과인 35를 return합니다.

 

 

 

 

Lambda 식을 이용하여 위 코드를 변경해봅시다.

 

 

 

일단 먼저 Lambda 식을 어떤 방식으로 사용할 수 있는지 알아봅시다.

 

Lambda 식은 총 2가지의 형태로 사용할 수 있습니다.

 

 

(parameters) => expression;

 

Lambda의 첫 번째 형태는 위처럼 parameter와 expression을 Lambda 기호(=>)로 연결한 형태입니다.

왼쪽 parameters 위치에는 Lambda 식이 받을 인자들의 정보가 위치하며

오른쪽 expression 위치에는 Lambda 식의 기능에 대한 내용이 들어갑니다.

 

Lambda도 일종의 함수이기 때문에 당연히 어떤 parameter를 받고 어떤 기능을 가질지에 대한 내용이 모두 존재합니다.

다만 한 가지 차이는 함수의 이름이 없죠.

아까 위에서 Lambda를 무명 함수(Anonymous Function)이라고 했던 이유가 바로 여기에 있습니다.

함수의 이름 없이 어떠한 기능을 구현할 수 있는 것이지요.

 

 

 

 

(parameters) => {
    expression
};

 

Lambda의 두 번째 형태는 위와 같습니다.

첫 번째 형태와 거의 비슷하지만 expression 부분이 중괄호{}로 감싸져있습니다.

 

사실 첫 번째 Lambda 식의 형태와 큰 차이는 없으나 Lambda 식에 좀 더 복잡한 기능을 구현하기 위해 Lambda 식의 expression 부분을 중괄호{}로 감쌀 수 있습니다.

 

 

 

 

 

using System;

class Sample2
{
    public static void Main()
    {
        Func<int, int, int> multiply = (x, y) => x * y;
        Console.WriteLine(multiply(5, 7));
    }
}



-- Result
35

 

Lambda 식을 이용한 예시입니다.

 

- Func<int, int, int> multiply = (x, y) => x * y;

Lambda 식을 이용한 부분입니다.

부분별로 살펴봅시다.

 

 

- Func<int, int, int>

일단 Lambda 식에서 받는 인자는 (x, y) 2개입니다. 그리고 x, y는 모두 int 입니다.

 

Func<int, int, int> 에서 가장 마지막(가장 오른쪽에 있는) int는 Lambda 식의 결과로서 return되는 값의 data type을 의미합니다.

 

그리고 나머지 첫 번째, 두 번째 int는 Lambda parameter의 data type입니다.

Lambda가 x, y 2개의 인자를 받으며 x, y는 모두 int이므로 첫 번쨰, 두 번째 data type이 모두 int입니다.

 

 

 

- multiply = (x, y) => x * y

그리고 이 부분이 Lambda를 본격적으로 사용하는 부분입니다.

일단 (x, y) 2개의 인자를 받아서 x * y 값을 return하는 기능을 구현한 것입니다.

그리고 이 기능을 multiply라는 변수에 저장하라는 의미이죠.

 

근데 여기서 좀 이해가 안되는 부분이 있을겁니다.

Lambda 식은 무명 함수라고 했으면서 왜 multiply라는 변수에 할당하여 이름이 가진 함수로 설정하는 것일까요?

 

Lambda는 이름이 없는 무명 함수이므로 Lambda 식을 단독으로 호출할 순 없습니다.

함수 호출을 위해선 반드시 함수의 이름이 있어야합니다.

 

따라서 위 예시에서는 Lambda가 기능을 제대로 구현하는지 보기 위해 Lambda식을 multiply라는 변수에 할당한 것 뿐입니다.

(x, y) => x * y 즉, 이 부분만으로 이미 Lambda 식은 parameter 설정부터 기능 구현까지 모두 완성된 것이라고 볼 수 있습니다.

 

 

 

 

 

 

 

 

 

 

 

using System;

class Sample2
{
    public static void Main()
    {
        Func<int, int, int> multiply = (x, y) =>
        {
            return x * y;
        };
        Console.WriteLine(multiply(5, 7));
    }
}


-- Result
35

 

이번에는 Lambda의 두 번째 형태를 이용한 예시입니다.

거의 대부분이 동일합니다만 Lambda 기호(=>)의 오른쪽에 중괄호{}가 있으며 이 중괄호 안에 Lambda 식의 기능 부분이 존재합니다.

 

결과와 기능은 동일합니다.

 

 

 

 

 

 

그러면 이런 Lambda 식은 왜 이용할까요?

여러 경우가 있겠지만 가장 대표적으로 언급되는 이유는 바로 간편성입니다.

 

위에서 봤던 예시 중 Lambda 식을 사용하지 않은 경우와 사용한 경우를 비교해봅시다.

 

using System;

class Sample2
{
    public static void Main()
    {
        int multiply(int x, int y)
        {
            return x * y;
        }
        Console.WriteLine(multiply(5, 7));
    }
}

 

using System;

class Sample2
{
    public static void Main()
    {
        Func<int, int, int> multiply = (x, y) => x * y;
        Console.WriteLine(multiply(5, 7));
    }
}

 

 

같은 기능을 함에도 multiply라는 함수의 기능을 만을 때 일반적인 함수 생성방식보다 Lambda를 이용한 방식이 코드의 줄 수가 더 적습니다.

또한 가독성도 좀 더 직관적이죠.

 

물론 위 경우는 사실 어떤 방식을 사용하건 크게 체감이 되지 않을 수 있습니다.

왜냐면 코드 자체가 간단하기때문이죠.

근데 수많은 기능을 가진 함수들이 있고, 코드의 라인이 많아지면 많아질수록 Lambda를 이용하면 좀 더 가독성 있고 간단한 코드를 작성할 여지가 많아질겁니다.

 

 

 

 

 

그리고 Lambda 함수는 C#에서 기본적으로 정의되는 deligate을 이용할때도 유용합니다.

 

대표적으로 Paint Event를 예시로 들업봅시다.

 

using System;
using System.Windows.Forms;
using System.Drawing;

class Sample2
{
    public static void Main()
    {
        Form fm = new Form();
        fm.Width = 500;
        fm.Height = 400;

        void paint_ellipse(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;

            g.FillEllipse(new SolidBrush(Color.Gray), 10, 20, 150, 100);
        }
        fm.Paint += new PaintEventHandler(paint_ellipse);

        Application.Run(fm);
    }
}

 

 

위 코드는 Form에 타원을 그리는 코드입니다.

 

 

 

        void paint_ellipse(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;

            g.FillEllipse(new SolidBrush(Color.Gray), 10, 20, 150, 100);
        }
        fm.Paint += new PaintEventHandler(paint_ellipse);

 

타원을 그리는 Event method를 보면 위와 같습니다.

 

- g.FillEllipse(new SolidBrush(Color.Gray), 10, 20, 150, 100);

SolidBrush를 사용하였고, 색상은 Gray로 하였습니다. (그래서 회색 타원이 그려졌습니다.)

 

10, 20은 각각 Ellipse의 x, y좌표를 의미합니다. x=10, y=20 위치에 Ellipse를 그리라는 의미입니다.

 

150, 100은 각각 Ellipse의 가로 길이, 세로 길이를 의미합니다.

 

 

여기까진 크게 문제가 없죠.

근데 만약 외부의 어떤 새로운 인자를 Event method에 전달해서 Ellipse의 x, y좌표를 업데이트해야하는 상황이라면 어떨까요?

Event를 사용하다보면 Event method 내부에서 어떤 다른 외부의 변수를 참조해야 할 때가 있는데 이때 Lambda 식을 이용할 수 있습니다.

 

 

using System;
using System.Windows.Forms;
using System.Drawing;

class Sample2
{
    public static void Main()
    {
        Form fm = new Form();
        fm.Width = 500;
        fm.Height = 400;

        int ellipse_x = 10;
        int ellipse_y = 20;

        void paint_ellipse(object sender, PaintEventArgs e, int x, int y)
        {
            Graphics g = e.Graphics;

            g.FillEllipse(new SolidBrush(Color.Gray), x, y, 150, 100);
        }
        fm.Paint += new PaintEventHandler(paint_ellipse);

        Application.Run(fm);
    }
}

 

그런 경우 코드를 위처럼 수정할 수 있습니다.

paint_ellipse method에 x, y 인자를 추가하고, FillEllipse method에서 x, y좌표를 x, y parameter를 참조하도록 하는 것입니다.

 

근데 위 코드는 Error가 발생합니다.

 

 

 

 

fm.Paint += new PaintEventHandler(paint_ellipse);

 

그 이유는 바로 PaintEventHandler에 paint_ellipse method를 할당하는 경우입니다.

 

PaintEventHandler는deligate로서 인자로 전달된 method에 object, PaintEventArgs 2개의 인자를 전달합니다.

그래서 기본적으로 paint_ellipse method는 object sender, PaintEventArgs e 이렇게 2개의 인자를 받습니다.

 

근데 paint_ellipse method에 int x, int y 이렇게 2개의 인자가 추가되었습니다.

PaintEventHandler는 object, PaintEventArgs 2개의 인자만을 전달하는데 paint_ellipse method는 총 4개의 인자를 받아야하므로 parameter의 불일치가 발생하는 것이죠.

 

그럴 때 Lambda 식을 이용해서 어떠한 함수의 이름도 없이 PaintEventHandler의 parameter에서 총 4개의 인자를 가지는 새로운 함수를 전달할 수 있습니다.

 

 

 

using System;
using System.Windows.Forms;
using System.Drawing;

class Sample2
{
    public static void Main()
    {
        Form fm = new Form();
        fm.Width = 500;
        fm.Height = 400;

        int ellipse_x = 10;
        int ellipse_y = 20;

        void paint_ellipse(object sender, PaintEventArgs e, int x, int y)
        {
            Graphics g = e.Graphics;

            g.FillEllipse(new SolidBrush(Color.Gray), x, y, 150, 100);
        }
        fm.Paint += new PaintEventHandler((sender, e) => paint_ellipse(sender, e, ellipse_x, ellipse_y));

        Application.Run(fm);
    }
}

 

코드를 제대로 수정하면 위와 같습니다.

paint_ellipse method외부에 ellipse_x, ellipse_y라는 변수를 선언해뒀습니다. 이것은 타원의 x, y 좌표를 의미합니다.

 

 

 

        void paint_ellipse(object sender, PaintEventArgs e, int x, int y)
        {
            Graphics g = e.Graphics;

            g.FillEllipse(new SolidBrush(Color.Gray), x, y, 150, 100);
        }

 

그리고 paint_ellipse method에 x, y인자를 추가하여 FillEllipse가 x, y 인자를 참조하여 Ellipse의 위치를 지정하도록 합니다.

 

 

 

        fm.Paint += new PaintEventHandler((sender, e) => paint_ellipse(sender, e, ellipse_x, ellipse_y));

 

마지막으로 PaintEventHandler에 paint_ellipse method를 전달할 때 Lambda 식을 이용합니다.

여기서 어떤식으로 진행되는지 한번 단계별로 살펴봅시다.

 

1. PaintEventHandler는 object, PaintEventArgs 2개의 인자를 발생시킴.

 

2. (sender, e) 이 부분은 Lambda 식에서 parameter를 명시하는 부분임. PaintEventHandler가 발생시키는 첫 번째 인자인 object는 sender에 전달되며, PaintEventArgs는 e에 전달됨.

 

3. => Lambda 식을 이용함.

 

4. paint_ellipse method의 첫 번째 인자인 sender에는 object 객체가 전달됨.

paint_ellipse method의 두 번째 인자인 e로는 PaintEventArgs 객체가 전달됨.

paint_ellipse method의 세 번째, 네 번째 인자로는 ellipse_x, ellipse_y를 전달받도록 함. (ellipse_x, ellipse_y는 코드 상단에 선언된 변수값임).,

 

5. 이렇게 Lambda로 생성된 함수를 PaintEventHandler에 등록함.

 

 

이렇게 복잡하게 하는 이유는 PaintEventHandler deligate는 무조건 object, PaintEventArgs 2개의 객에만 전달하기 때문에 그 외의 추가적인 인자를 받는 paint_ellipse 같은 method를 PaintEventHandler에 등록하기 위해서는 PaintEventHandler 내부에서 Lambda를 이용하여 paint_ellipse에 전달되는 인자들의 종류와 순서를 명확하게 전달해줘야합니다.

 

 

 

 

 

 

 

 

 

using System;
using System.Windows.Forms;
using System.Drawing;

class Sample2
{
    public static void Main()
    {
        Form fm = new Form();
        fm.Width = 500;
        fm.Height = 400;

        int ellipse_x = 10;
        int ellipse_y = 20;

        void paint_ellipse(object sender, PaintEventArgs e, int x, int y)
        {
            Graphics g = e.Graphics;

            g.FillEllipse(new SolidBrush(Color.Gray), x, y, 150, 100);
        }
        fm.Paint += new PaintEventHandler((sender, e) => paint_ellipse(sender, e, ellipse_x, ellipse_y));


        void click_event(object sender, EventArgs e)
        {
            ellipse_x = ellipse_x + 10;
            ellipse_y = ellipse_y + 10;
            fm.Invalidate();
        }
        fm.Click += new EventHandler(click_event);

        Application.Run(fm);
    }
}

 

여기서 click event가 발생할 때 마다 ellipse_x, ellipse_y에 각각 10씩 더해주는 Event를 추가하면 Form을 클릭할 때 마다 Ellipse의 위치가 변경되어 그려지는 것을 볼 수 있습니다.

 

이렇게 Event를 사용하다보면 Event method 내부에서 어떤 다른 외부의 변수를 참조해야 할 때가 있는데 이때 Lambda 식을 이용할 수 있습니다.

 

 

 

 

 

 

728x90
반응형
Comments