상세 컨텐츠

본문 제목

[C#] 대리자와 이벤트

.NET/C#

by 클리엘 클리엘 2021. 10. 18. 11:11

본문

728x90

1. 대리자 (델리게이트)

 

만약 아래와 같이 숫자를 계산해 주는 메서드가 존재할 때

class Cal
{
    public int Sum(int i, int j)
    {
        return i + j;
    }
}

이 메서드를 호출해 계산을 수행하려면 다음과 같이 메서드를 호출할 수 있습니다.

class Program
{
    static void Main(string[] args)
    {
        Cal c = new Cal();
        WriteLine(c.Sum(100, 200));
    }
}

에제에서 메서드를 호출하기 위해 c.Sum(100, 200)처럼 하였는데 이렇게 직접적으로 메서드를 호출하는 것이 아니라 다른 매개체를 통해서 필요한 메서드를 대신 호출할 수도 있는데 이때 사용되는 매개체를 '대리자'라고 합니다. 즉, 대리자를 통해서 다른 메서드를 대신 메서드를 호출하는 것입니다.

 

대리자는 아래와 같이 선언됩니다.

private delegate int CalDelegate(int i, int j);

'[한정자] delegate [반환 형식] [delegate이름] (매개변수)'형식이며 특히 반환 형식과 매개변수 형식은 대신 호출하려는 메서드의 구조와 일치해야 합니다.

 

위에서 처럼 선언된 대리자를 통해 메서드를 호출하려면 대리자의 인스턴스를 생성할 때 메서드 자체를 생성자의 매개변수로 전달하고 이후에 메서드 대신 메서드와 동일한 방식으로 대리자를 호출해주면 됩니다.

class Program
{
    private delegate int CalDelegate(int i, int j);

    static void Main(string[] args)
    {
        Cal c = new Cal();
        CalDelegate cd = new CalDelegate(c.Sum);

        WriteLine(cd(100, 200));
    }
}

예제에서는 Sum()이라는 메서드만 대리자를 통해 호출하고 있습니다.

 

메서드를 직접 호출하지 않고 대리자를 사용하는 이유는 메서드 자체를 다른 메서드가 매개변수로 넘겨 메서드의 필요한 기능을 수행하기 위함입니다. 예를 들어 아래와 같은 2개의 메서드가 존재할 때

class Cal
{
    public int Sum(int i, int j)
    {
        return i + j;
    }

    public int Minus(int i, int j)
    {
        return i - j;
    }
}

뎃셈을 수행할 것인가 혹은 뺄셈을 수행할 것인가에 따라 각각의 메서드를 호출하면 되지만 아래와 같이 대리자를 받는 메서드를 만들어 놓게 되면

class Program
{
    private delegate int CalDelegate(int i, int j);

    static void Main(string[] args)
    {
        Cal c = new Cal();
        CalDelegate cd = new CalDelegate(c.Sum);

        Print(100, 200, cd);

        cd = new CalDelegate(c.Minus);

        Print(200, 100, cd);
    }

    static void Print(int i, int j, CalDelegate cd)
    {
        WriteLine($"결과는 {cd(i, j)} 입니다.");
    }
}

Sum()이든 Minus()든 해당 기능이 필요한 경우마다 '대리자'를 매개변수로 전달하여 원하는 동작을 수행할 수 있는 것입니다. 이렇든 대리자는 반환 형식과 매개변수 형식만 일치하면 어떤 메서드든 대리자를 통해 메서드를 호출할 수 있습니다. 대리자는 메서드의 참조로서 메모리상에 있는 메서드의 주소를 가지고 대상 메서드를 호출해주는 역할을 하는 것입니다.

 

● 대리자의 일반화

 

이전에 아래 글을 통해 메서드를 일반화하는 것에 대해서 알아보았습니다.

[.NET/C#] - [C#] 일반화 프로그래밍

 

[C#] 일반화 프로그래밍

일반화 프로그래밍은 처리의 대상이 되는 '데이터'에서 '타입'이라는 개념만을 분리해 공통적으로 취급하고자 하는 것을 말합니다. 예를 들어 다음과 같이 매개변수를 받은 정수 1개를 그대로

lab.cliel.com

일반화 메서드 역시 대리자를 통해서 호출할 수 있는데 이런 경우 메서드뿐만 아니라 대리자도 일반화를 위해서 형식 매개변수를 넣어줘야 합니다. 따라서

class Cal
{
    public T Print<T>(T i)
    {
        return i;
    }
}

위와 같은 일반화 메서드가 존재하는 경우에 이를 대리자로 호출하려면

class Program
{
    private delegate T CalDelegate<T>(T i);

    static void Main(string[] args)
    {
        Cal c = new Cal();
        CalDelegate<int> cd = new CalDelegate<int>(c.Print);

        Print<int>(100, cd);
    }

    static void Print<T>(T i, CalDelegate<T> cd)
    {
        WriteLine($"입력값은 {cd(i)} 입니다.");
    }
}

델리게이트의 형식을 일반화 메서드와 동일하게 맞춰주고 대리자의 인스턴스를 생성할 때나 다른 메서드의 매개변수로  전달이 필요할 때 형식 매개 변수만 추가해서 맞춰주면 다른 메서드처럼 일반화 메서드도 대리자를 통해 호출할 수 있게 됩니다.

 

● 대리자 체인

 

하나의 대리자는 반드시 하나의 메서드가 아닌 여러 개의 메서드를 참조할 수 있습니다. 이런 상태에서 대리자를 사용하게 되면 참조된 모든 메서드를 차례로 호출하게 됩니다.

class Program
{
    private delegate void CalDelegate(int i, int j);

    static void Main(string[] args)
    {
        Cal c = new Cal();
        CalDelegate cd = new CalDelegate(c.Sum);
        cd += new CalDelegate(c.Minus);

        cd(100, 200);
    }
}


class Cal
{
    public void Sum(int i, int j)
    {
        WriteLine($"결과는 { i + j} 입니다.");
    }

    public void Minus(int i, int j)
    {
        WriteLine($"결과는 { i - j} 입니다.");
    }
}

이렇게 하나의 대리자에 여러개의 메서드가 연결된 상태를 '대리자 체인'이라고 합니다. 대리자의 체인은 += 연산자를 통해 가능하며 기타 아래와 같은 방법으로도 체인을 구성할 수 있습니다.

CalDelegate cd = new CalDelegate(c.Sum) + new CalDelegate(c.Minus);
//또는
CalDelegate cd = (CalDelegate)Delegate.Combine(new CalDelegate(c.Sum), new CalDelegate(c.Minus));

반대로 체인에서 특정 메서드를 제거해야 하는 경우에는 -=연산자를 사용합니다.

class Program
{
    private delegate void CalDelegate(int i, int j);

    static void Main(string[] args)
    {
        Cal c = new Cal();
        CalDelegate cd = new CalDelegate(c.Sum);
        cd += new CalDelegate(c.Minus);

        cd(100, 200);

        cd -= c.Sum; //체인에서 제거

        cd(200, 100);
    }
}

혹은 Delegate의 Remove() 메서드를 사용할 수도 있습니다.

cd = (CalDelegate)Delegate.Remove(cd, new CalDelegate(c.Sum));

● 익명 메서드 연결

 

대리자는 기존에 존재하는 메서드가 아닌 익명 메서드 자체를 연결할 수도 있습니다.

class Program
{
    private delegate int CalDelegate(int i, int j);

    static void Main(string[] args)
    {
        CalDelegate c = delegate (int i, int j)
        {
            return i + j;
        };

        WriteLine(c(100, 200));
    }
}

물론 대리자도 익명 메서드와 반환형식과 매개변수 형식이 동일해야 하고 이 상태에서 대리자가 호출될 때 연결된 익명 메서드를 실행합니다.

 

2. 이벤트

 

이벤트는 프로그램에서 어떤 사건이 발생하는 것을 말합니다. 쉽게는 마우스로 버튼을 클릭한다던가 키보드로 어떤 내용을 입력하는 것 등이 모두 이벤트에 해당합니다. 이러한 이벤트를 C#에서는 대리자를 통해 처리합니다.

 

우선 이벤트를 사용하기 위해 클래스 밖에서 이벤트 델리게이트를 선언합니다.

public delegate void EventDelegate();

그다음 이벤트를 처리할 새로운 클래스를 만들어 위에서 선언한 이벤트 델리게이트의 인스턴스를 event한정자를 통해서 수식합니다.

 

그리고 클래스 안에서 해당 인스턴스를 호출하는 메서드를 추가합니다.

class EventNotifier
{
    public event EventDelegate EventInstance;

    public void OccurEvent()
    {
        EventInstance();
    }
}

위에서 선언한 클래스를 통해 이벤트를 발생시키려면 실제 이벤트를 일으킬 이벤트 핸들러 메서드를 만들고 위에서 선언한 클래스의 인스턴스를 생성하여 내부의 이벤트 인스턴스에 작성해둔 이벤트 핸들러 메서드를 등록합니다.

class Program
{
    static void Main(string[] args)
    {
        EventNotifier eh = new EventNotifier();
        eh.EventInstance += new EventDelegate(EventHandler);

        eh.OccurEvent();
    }

    static public void EventHandler()
    {
        WriteLine("이벤트 발생");
    }
}

마지막으로 이벤트 클래스의 메서드를 호출하게 되면 이벤트 인스턴스에 등록된 메서드를 호출하면서 '이벤트 발생'이라는 문장을 출력하게 됩니다.

 

대리자와 이벤트가 별 차이 없어 보이지만 이벤트(EventInstance)는 대리자와 달리 public으로 수식되어 있어도 외부에서 임의적으로 호출할 수 없습니다. 반드시 어떤 상태변화가 발생해야만 이벤트가 호출되어 이를 알리는 용도로 사용하는 것입니다.

 

728x90

'.NET > C#' 카테고리의 다른 글

[C#] LINQ  (0) 2021.10.19
[C#] 람다식  (0) 2021.10.19
[C#] 대리자와 이벤트  (0) 2021.10.18
[C#] 예외처리  (0) 2021.10.15
[C#] 일반화 프로그래밍  (0) 2021.10.14
[C#] 배열, 컬렉션, 인덱서  (0) 2021.10.13

관련글 더보기

댓글 영역