본문 바로가기

IT 살이/04. 기술 - 프로그래밍

LINQ 시리즈 02 - 익명 메소드(anonymous method )

앞의 포스트에서 다음 코드를 보았다.

delegate void SimpleDelegate();

public class Writer

{

    public string Text;

    public int Counter;

    public void Dump()

    {

        Console.WriteLine(Text);

        Counter++;

    }

}


public class DemoDelegate

{

    void Repeat10Times(SimpleDelegate somework)

    {

        for (int i = 0; i < 10; i++)

            somework();

    }

    void Run1()

    {

        Writer writer = new Writer();

        writer.Text = "C# demo";

        this.Repeat10Times(writer.Dump);

        Console.WriteLine(writer.Counter);

    }

}

이 코드에서는 SimpleDelegate에 의해서 실행될 코드와 인자 역할을 하는 데이터를 가지고 있는 Writer 클래스가 정의되어 있다. 이것을 없애고 다음과 같은 표현으로 변경될 수 있다.

delegate void SimpleDelegate();

public class DemoDelegate

{

    void Repeat10Times(SimpleDelegate somework)

    {

        for (int i = 0; i < 10; i++)

            somework();

    }

    void Run1()

    {

        int counter = 0;

        this.Repeat10Times(

            delegate

        {

           Console.WriteLine("C# demo");

           counter++;

        }

        );

        Console.WriteLine(writer.Counter);

    }

}

Repeat10Times()의 인자로서 메소드명이 들어갈 자리에  메소드를 정의하고 있는 코드가 들어가 있다. 그리고 데이터 멤버 counter는 Repeat10Times()의 래퍼 메소드에 포함되어 있다.  C# 컴파일러는 delegate 키워드 이하의 블럭{...}사이의 코드를 이용해서 내부적으로 Writer와 유사한 클래스와 메소드를 만들어낸다. 그리고 그 인스턴스를 만들고 그것의 메소드에 대한 포인터를 Repeat10Times()의 인자로 넘긴다. Repeat10Times()입장에서는 넘어오는 인자의 메소드명이나 클래스명이 중요한 것이 아니다. SimpleDelegate 델리게이트 타입이 정의하고 있는 시그너쳐와 동일한 메소드 포인터가 넘어오는가가 중요하다. 그리고 실행될 메소드의 코드에 대한 포인터라는 것이 중요하다. 이 말은 앞의 코드의 모양이 Repeat10Time()의 인자로 코드가 넘어가는 듯한 모양이지만, 실제 내부적으로는 그 코드가 정의되어 있는 곳의 포인터가 넘어간다는 것이다. 그 포인터의 이름은 어떻든 상관없다.

delegate 키워드와 블럭 {}에 의해서 정의된 메소드를 이름이 없는 메소드 즉 익명 메소드(anonymous method)라고 한다.

인자가 필요한 익명 메소드를 정의해보면 다음과 같다.

delegate void TowParamsDelegate(string text, int age);

public class DemoDelegate

{

    void Repeat10Times(TowParamsDelegate somework)

    {

        for (int i = 0; i < 10; i++)

            somework("C# demo", i);

    }

    void Run1()

    {

        this.Repeat10Times(

            delegate( string text, int age )

        {

            Console.WriteLine("{0}, {1}", text, age);

        }

        );

    }

}

모양이 아직까지는 좀 우습게 보이지만 다음에 멋지게 변한다. 멋지다는 것은 아주 직관적으로 변하게 된다는 것이다. 즉 코드만 보면 무슨 말인지 바로 알 수 있는 모습으로 말이다.

다시 한번 더 LINQ 쿼리 표현을 보자.

var query =

        from c in customers

        where c.Discount > 3

        orderby c.Discount

        select new { c.Name, Perc = c.Discount / 100 };

다음은 동일한 C# 표현이다.

var query = customers

                   .Where(c => c.Discount > 3)

                   .OrderBy(c => c.Discount)

                   .Select(c => new { c.Name, Perc = c.Discount / 100 });


Where() 메소드의 인자로 넘어가고 있는 "c=>c.Disount>3" 부분이 바로 익명 메소드 표현이 진화해서 된 것이다. 인자와 메소드의 바디 부분이 이곳에 모두 표시되어 있다. 단지 메소드의 이름이 없을 뿐이다.
그러나 아직 이 표현을 모두 이해할 준비는 되지 않았다. 이제 다음 포스트에서 제너릭, Type inference( 타입 추론?), 람다 표현(lamda expression)에 대해서 설명한다. 그러고도 몇 가지 더 배워야만 이 표현을 이해할 수 있게 될 것이다.

설명을 이렇게 하고 싶지 않았는데! 머릿속에 그림을 그려가면서 이해할 수 있도록 하고 싶었는데. 시간이 없고 마음이 급하다 보니 또 이렇게 형식적으로 흘러간다. 써글!