본문 바로가기

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

LINQ 시리즈 01 - 델리게이트(delegate )

앞에서 얘기한대로 이제 C#언어에 대해서 알아보겠다. LINQ 쿼리문을 이해하기 위해서 필요한 C#언어 요소들을 하나씩 알아가보겠다. 먼저 델리게이트 ! 델리게이트하면 필자에게는 다음과 같은 내용이 떠오른다.

▶클래스와 같은 일종의 타입이다. 다음과 같은 방법으로 델리게이트 타입을 정의한다.

delegate void TwoParamsDelegate(string name, int age);

▶이것도 타입이니 인스턴스를 만들 수 있다.

TwoParamsDelegate c = new TwoParamsDelegate(인자);

▶델리게이트 타입의 객체에는 다른 객체의 메소드에 대한 포인터가 할당된다.

앞의 TwoParamsDelegate 델리게이트 타입은 파라미터로 string, int 타입의 값을 받고 void를 반환하는 메소드를 할당받을 수 있다.

public Class1()

{

    DemoDelegate d = new DemoDelegate();

 

    TwoParamsDelegate c = new TwoParamsDelegate(d.MethodC);

}

이때 DemoDelegate 객체의 수명은 TowParamsDelegate 인스턴스의 객체만큼 길어진다. 델리게이트 인스턴스가 장수하면 그 델리게이트가 참조하고 있는 객체도 그 만큼 장수하게 된다.

이런 이유로 해서 정적(static) 멤버인 델리게이트에 메소드를 할당할때는 주의해야 한다는 것이다. 자세한 것은 "이벤트 핸들러에 의한 메모리 증가(http://humanvirus.me/182)" 포스트를 참조하자. 이벤트 멤버도 델리게이트 타입이라는 것을 알고 있을 것이다. 모르면 지금 알면 된다. -_-;; 이 포스트에서는 웹폼/윈폼에서 static 델리게이트를 잘못 사용하면 페이지가 한번 로딩한 다음 계속해서 메모리에서 내려가지 않을 수 있다는 것을 말하고 있다.

▶중요한 것은 그 할당된 포인터를 통해서 해당 메소드를 호출할 수 있다는 것이다.

c("달봉이", 20 ); //^^

LINQ를 공부하기 위해서는 여기까지가 중요하다.

델리게이트를 좀 더 생각해보면 델리케이트에는 메소드 포인터를 하나만 할당할 수 있는 것이 아니라 여러 개의 포인터를 추가할 수 있다. 따라서 포인터를 할당한다기 보다는 정확히는 포인터를 추가한다는 표현이 옳을 것이다. 이벤트 멤버도 특수한 델리게이트 객체인데, 이벤트에 여러 개의 핸들러를 "+=" 연산자를 이용해서 추가한 것을 생각하면 된다.
또 하나가 있는데….뭐였드라….에이 까먹었다.

앞에서는 델리게이트 인스턴스를 생성할때 new 키워드를 사용했다.

앞의 포스트의 LINQ 표현을 다시 보자. 그러나 메소드 명만을 지정할 수도 있다.

public Class1()

{

    DemoDelegate d = new DemoDelegate();

    TwoParamsDelegate c = d.MethodC;

}

C#컴파일러는 코드를 통해서 델리게이트 타입을 추론한다. 그래서 new TwoParamsDelegate 코드를 만들어낸다.  그래서 IL코드가 될때는 new를 사용할때와 동일한 코드가 된다.

IL코드가 뭔지는....? .NET 언어로 작성된 코드는 컴파일을 하면 일단 중간 단계의 IL 코드로 변한다. Visual Basic으로 작성하든 C#으로 작성하든 모두 IL코드로 컴파일되는데, IL단계의 코드는 실제로 실행될때 JIT컴파일러에 의해서 다시 머신코드로 변한다.   왜 이런 단계를 두었냐고 물으신다면..오늘은 머리가 딸린다.   그냥 넘어가자.         

델리게이트는 흔히 기존의 코드에 어떤 코드를 끼워 넣는 경우 자주 사용된다.

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라는 델리게이트 타입을 하나 정의하고있다. 그리고 DemoDelegate 클래스의 Repeat10Times() 메소드의 파라미터로 받아들여서 해당 델리게이트 인스턴스가 가리키는 메소드를 10번 호출하고 있다. SimpleDelegate 델리게이트 타입이 정의하고 있는 시그너쳐에 맞는 메소드는 어떤 메소드든지 Repeate10Times()의 인자로 넘겨져서 10번 호출되는 것이다. 그러나 앞의 Writer 클래스를 없애고 좀 더 간단한 표현으로 변경될 수 있다. 다음 포스트의 주제이다.

델리게이트가 LINQ 쿼리문의 어디에서 사용되는지 미리 함 보자.

var query =

                from c in customers

                where c.Discount > 3

                orderby c.Discount

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

이처럼 SQL 과 비슷한 표현을 쿼리 표현(query expression)이라고 한다. 이 표현은 컴파일러에 의해서 C#언어가 이해할 수 있는 다음과 같은 표현으로 변한다. 만약 Visual Basic을 사용하면 다른 표현으로 변경될 것이다.

var query = customers

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

                   .OrderBy( c=>c.Discount )

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

이 표현중에서 customers.Where(c => c.Discount > 3)은 익히 알고 있는대로, customers라는 객체에 Where()라는 메소드가 호출되는 것이고 인자로 "c=>c.Discount>3"이라는 인자가 넘어가는 평범한 문장이다. 그리고 그 반환된 결과에 OrderBy() 메소드가 호출되는 것이고...이렇게 계속된다.

이곳에서 "=>"가 포함된 표현 "c=>c.Discount > 3"은 델리게이트가 진화해서 된 부분임을 알게 된다. 델리게이트가 어떻게 진화할 지 아직 얼른 감이 오지 않을 것이다. 그럼 다음 포스트에서 보자.