본문 바로가기
C# 은 MSDN이 정답이다.

 

함수 & 매개변수


학습목표

언제나 그렇듯 프로그래밍 학습은 개발자 관점에서 대리자(delegate)라는 기술이, 

프로그래밍에서 어떤 기존 개발 방법을 조금이라도 쉽고, 편리하게 하기 위한 것일까? 를 파악하는 것이 목표다.

 

 

학습순서

  1. Callback 개념과 Delegate 용어
  2. 대리자를 사용하는 이유
  3. Delegate를 선언하고 사용하는 방법
  4. 일반화 대리자를 사용하는 방법
  5. Multicast Delegete (대리차 체인)

 

01. Callback 개념과 Delegate 용어

 

01.1 Callback 의 구조

 

 

 

01.2 C# 의 Callback 의 구조

 

 

 

01.3 Delegate 용어

 

Delegate 라는 용어는, 한국어로 '대리자', '대리인'을 의미합니다.

대신해서 일해주는 것을 전문으로 하는 사람을 의미합니다.

 

C, C++ 은, 변수 대신에 포인터와 참조를 사용했습니다.

C# 은, 포인터는 없지만 참조를 변수 대신 사용할 수 있습니다.

 

그리고 C# 은, 함수의 참조를 함수 대신 사용할 수 있습니다.

함수의 참조값을 가진 참조 변수를 Delegate 변수라고 합니다.

 

 

01.4 정리

 

Delegate 학습을 시작할 때, 명심해야 할 점은 다음과 같습니다.

 

- delegate 는 프로그래밍에서 어떤 점을 편리하게 하는가?

 

- 일반적으로 함수를 사용할 때, 매개변수로 을 전달 할 수 있고, "전달하는 값"에 따라 함수의 결과가 달라진다. 

Console.WriteLine("매개변수 값");

 

- delegate 는 함수를 사용할 때, 매개변수에 로직을 전달 할 수 있고, "전달하는 로직"이 다르다면 어떻게 되는가?  

Console.WriteLine("대리자 함수 로직");

 

 

02. 대리자를 사용하는 이유

함수에 값이 아닌 '로직' 자체를 매개변수 처럼 전달하고 싶을 때 사용합니다.

C++ Qt 에서 커넥트의 시그널과 슬롯처럼 사용합니다.

C++ Qt 에서 커넥트 기능이 없으면 커넥트와 동일한 구조를 어떻게 구현 할 수 있나요?

 

 

03. 대리자 사용법

 

03.1 기본 사용법

 

 

03.1.1 선언방법

 

함수 헤더에 delegate 로 수식하면 된다.

 

[델리게이트_키워드]  [함수_반환형]  [델리게이트_이름] (함수_시그니처);

 delegate return_type delegate_name(int num1, int num2);

 

[.NET Frame 1.0]

델리게이트_키워드   함수_반환형  델리게이트_이름  =  new 델리게이트_키워드(함수_이름)

 delegate return_type delegate_name = new delegate(funtion);

 

[.NET Frame 2.0]

델리게이트_키워드 함수_ 반환형  델리게이트_이름  = 함수_이름

delegate void delegate_name = funtion;

 

 

03.1.2 시그니처

 

매개변수 타입, 매개변수 갯수, 리턴 타입 

함수의 헤더(function header)

 

 

03.1.3 대리자를 사용해, 함수를 변수처럼 사용 할 수 있다.

/* 03.Mar.2024
 * 변수명, 함수명 따라하지 말 것
 * 최소한 코딩 컨벤션을 지킬 것
 * https://learn.microsoft.com/ko-kr/dotnet/csharp/fundamentals/coding-style/coding-conventions
 */

namespace DelegateTest01
{
    // Plus, Minus 함수를 가진 Calculator 클래스
    class Calculator
    {
        public int Plus(int a, int b)
        {
            return a + b;
        }

        public static int Minus(int a, int b)
        {
            return a - b;
        }
    }

    /* Calculator 클래스의 Plus, Minus 함수와 
     * 동일한 시그니처를 가진 대리자 선언 */
    delegate int Connect(int a, int b);

    class MainClass
    {
        static void Main(string[] args)
        {
            /* [01]
             * Calculator 클래스의 Plus, Minus 함수 사용 */
            Calculator Calc = new Calculator();
            Console.WriteLine(Calc.Plus(3, 4));
            Console.WriteLine(Calculator.Minus(3, 4));


            /* [02]
             * Calculator 클래스의 Plus 함수 사용 */
            Console.WriteLine(Calc.Plus(10, 5));

            // 대리자 Callback을 Calculator 클래스의 Plus 함수 대신 사용
            Connect Callback1 = new Connect(Calc.Plus);
            Console.WriteLine(Callback1(10, 5));


            /* [03]
             * Calculator 클래스의 Minus 함수 사용 */
            Console.WriteLine(Calculator.Minus(20, 10));

            // 대리자 Callback을 Calculator 클래스의 Minus 함수 대신 사용
            Connect Callback2 = new Connect(Calculator.Minus);
            Console.WriteLine(Callback2(20, 10));
        }
    }
}

 

 

 

03.2 함수의 매개변수 사용법 비교

 

아래 나열된 3가지 매개변수 사용법과, 위 대리자 기본 사용법을 응용한다.

*함수의 매개변수 자리에는 변수 형태로 전달되어야 한다는 것과

*대리자는 매개변수를 전달하는 방법일 뿐임을 확인한다.

 

 

매개변수 사용법 1. 매개변수 자리에 변수를 사용해 값 전달

int num1 = 1;
int num2 = 2;

int Add(int a, int b)
{
    int result = 0;

    // 매개변수로 전달받은 두 값 계산
    result = a + b;

    return result;
}

 

매개변수 사용법  2. 매개변수 자리에 객체를 사용해 값 전달

class object_
{
    public int a;
    public int b;
}

int Add_obj(object_ obj)
{
    int result = 0;

    // 객체로 전달받은 두 값 계산
    result = (int)obj.a + (int)obj.a;

    return result;
}

 

매개변수 사용법  3. 매개변수에 대리자를 사용해 로직 전달

delegate int A(int a, int b);

int Add_dele(A func)
{
    int result = 0;

    // 매개변수로 전달받은 대리자를 사용한 계산
    result = func(1, 2);

    return result;
}

 

 

03.3 함수의 매개변수로 전달되는 값 사용법 비교

 

매개변수 사용법 1. 변수 값을 함수에 전달 하기 

namespace DelegateTest02
{
    class MainClass
    {
        int Plus(int a, int b)
        {
            int result = 0;

            // 매개변수로 전달받은 두 값 계산
            result = a + b;

            return result;
        }

        static void Main(string[] args)
        {
            // 매개변수 사용법 1. 변수 값을 함수에 전달 하기 
            int num1 = 1;
            int num2 = 2;

            MainClass A = new MainClass();
            Console.WriteLine(A.Plus(num1, num2));
        }
    }
}

 

 

매개변수 사용법  2. 객체의 변수 값을 함수에 전달 하기

namespace DelegateTest03
{
    class Object_
    {
        public int num1;
        public int num2;
    }

    class MainClass
    {
        int Plus(Object_ obj)
        {
            int result = 0;

            // 매개변수로 전달받은 두 값 계산
            result = obj.num1 + obj.num2;

            return result;
        }

        static void Main(string[] args)
        {
            // 매개변수 사용법 2. 객체의 변수 값을 함수에 전달 하기
            Object_ obj = new Object_();
            obj.num1 = 1;
            obj.num2 = 2;

            MainClass A = new MainClass();
            Console.WriteLine(A.Plus(obj));
        }
    }
}

 

 

매개변수 사용법  3. 함수 로직을 함수에 전달 하기

  • 함수를 실행시킬 때, 다른 함수도 실행되도록 만듦
namespace DelegateTest04
{
    class Object_
    {
        public int Plus(int a, int b)
        {
            return a + b;
        }
    }

    /* Object_ 클래스의 Plus 함수와 
     * 동일한 시그니처를 가진 대리자 선언 */
    delegate int Connect(int a, int b);

    class MainClass
    {
        static void Main(string[] args)
        {
            int num1 = 1;
            int num2 = 2;

            // 매개변수 사용법 2. 객체의 변수 값을 함수에 전달 하기
            Object_ obj = new Object_();

            // 대리자 Callback을 Object_ 클래스의 Plus 함수 대신 사용
            Connect Callback = new Connect(obj.Plus);
            Console.WriteLine(Callback(num1, num2));
            //Console.WriteLine(obj.Plus(num1, num2));
        }
    }
}
namespace DelegateTest05
{

    /* Object_ 클래스의 함수와 
     * 동일한 시그니처를 가진 대리자 선언 */
    delegate int Connect(int a, int b);

    class Object_
    {
        public int Plus(int a, int b)
        {
            return a + b;
        }
        public static int Minus(int a, int b)
        {
            return a - b;
        }

        public int Calc(int a, int b, Connect callaback)
        {
            int num1 = a;
            int num2 = b;

            return callaback(1,2);
        }

    }

    class MainClass
    {
        static void Main(string[] args)
        {
            int num1 = 1;
            int num2 = 2;

            Object_ obj = new Object_();

            if (num1 < num2)
                Console.WriteLine(obj.Calc(num1, num2, obj.Plus));
            else
                Console.WriteLine(obj.Calc(num1, num2, Object_.Minus));


            /* Q.문제
             * Object_ 클래스의 Calc() 함수를 수정하여
             * num1이 num2보다 크면 Plus()함수를 실행하고, 
             * num1이 num2보다 작으면 Minus()를 실행하는 함수를 구현하세요.
             */

        }
    }
}
namespace DelegateTest06
{

    /* [2] 
     * 일반함수 Plus(int a, int b)와 동일한 
     * 시그니처를 가진 대리자 선언 */
    delegate int Connect(int a, int b);

    class MainClass
    {
        /*[1] 일반함수*/
        static int Plus(int a, int b)
        {
            return a + b;
        }

        static void Main(string[] args)
        {
            Connect callback = new Connect(Plus);

            int res = Add(callback);
            Console.WriteLine("계산 결과: " + res);
        }

        /*[3] 대리자를 매개변수로 하는 함수*/
        static int Add(Connect func)
        {
            return func(1, 2);
        }
    }
}

 

 

 

매개변수 사용법  4.  대리자와 버블 정렬

 

버블 정렬

https://youtu.be/YbsQiiubO74?si=DCvRp8qu-k6ecHaX

 

/* 03.Mar.2024
 * 변수명, 함수명 따라하지 말 것, 최소한 코딩 컨벤션을 지킬 것
 * https://learn.microsoft.com/ko-kr/dotnet/csharp/fundamentals/coding-style/coding-conventions
 */

namespace DelegateTest08
{
    delegate int Compare(int a, int b);

    class Sort
    {
        public int AscendCompare(int a, int b)
        {
            if (a > b)
                return 1;
            else if (a == b)
                return 0;
            else
                return -1;
        }

        public int DescendCompare(int a, int b)
        {
            if (a < b)
                return 1;
            else if (a == b)
                return 0;
            else
                return -1;
        }

        public void BubbleSort(int[] DataSet, Compare Comparer)
        {
            int i = 0;
            int j = 0;
            int temp = 0;

            for (i = 0; i < DataSet.Length - 1; i++)
            {
                for (j = 0; j < DataSet.Length - (i + 1); j++)
                {
                    if (Comparer(DataSet[j], DataSet[j + 1]) > 0)
                    {
                        temp = DataSet[j + 1];
                        DataSet[j + 1] = DataSet[j];
                        DataSet[j] = temp;
                    }
                }
            }
        }
    }

    class MainApp
    {

        static void Main(string[] args)
        {
            Sort sort = new Sort();

            /*[1] 오름차순
             */
            int[] array = { 3, 7, 4, 2, 10 };

            Console.WriteLine("Sorting ascending...");
            sort.BubbleSort(array, new Compare(sort.AscendCompare));

            for (int i = 0; i < array.Length; i++)
                Console.Write($"{array[i]} ");


            /*[2] 내림차순
             */
            int[] array2 = { 7, 2, 8, 10, 11 };

            Console.WriteLine("\nSorting descending...");
            sort.BubbleSort(array2, new Compare(sort.DescendCompare));

            for (int i = 0; i < array2.Length; i++)
                Console.Write($"{array2[i]} ");

            Console.WriteLine();
        }
    }
}

 

 

04. 일반화 대리자 

namespace DelegateTest09
{
    delegate int Compare<T>(T a, T b);

    class Sort
    {
        public int AscendCompare<T>(T a, T b) where T : IComparable<T>
        {
            return a.CompareTo(b);
        }


        public int DescendCompare<T>(T a, T b) where T : IComparable<T>
        {
            return a.CompareTo(b) * -1;
        }

        public void BubbleSort<T>(T[] DataSet, Compare<T> Comparer)
        {
            int i = 0;
            int j = 0;
            T temp;

            for (i = 0; i < DataSet.Length - 1; i++)
            {
                for (j = 0; j < DataSet.Length - (i + 1); j++)
                {
                    if (Comparer(DataSet[j], DataSet[j + 1]) > 0)
                    {
                        temp = DataSet[j + 1];
                        DataSet[j + 1] = DataSet[j];
                        DataSet[j] = temp;
                    }
                }
            }
        }
    }

    class MainApp
    {

        static void Main(string[] args)
        {
            Sort sort = new Sort();

            /*[1] 오름차순
             */
            int[] array = { 3, 7, 4, 2, 10 };

            Console.WriteLine("Sorting ascending...");
            sort.BubbleSort<int>(array, new Compare<int>(sort.AscendCompare));

            for (int i = 0; i < array.Length; i++)
                Console.Write($"{array[i]} ");


            /*[2] 내림차순
             */
            int[] array2 = { 7, 2, 8, 10, 11 };

            Console.WriteLine("\nSorting descending...");
            sort.BubbleSort(array2, new Compare<int>(sort.DescendCompare));

            for (int i = 0; i < array2.Length; i++)
                Console.Write($"{array2[i]} ");

            Console.WriteLine();
        }
    }
}

 

 

 

05. Multicast Delegate(대리자 체인)

 

C++ Qt에서 Connect 의 Signal 과 Slot 을 1:1 로 연결하는 것이 아니라 1:多로 연결해 사용한것처럼

대리자도 동일한 사용 방법을 제공한다.

 

namespace DelegateTest01_1
{
    // Plus, Minus 함수를 가진 Calculator 클래스
    class Calculator
    {
        public void Plus(int a, int b)
        {
            Console.WriteLine("Plus " + (a + b));
        }

        public static void Minus(int a, int b)
        {
            Console.WriteLine("Minus " + (a - b));
        }
    }

    /* Calculator 클래스의 Plus, Minus 함수와 
     * 동일한 시그니처를 가진 대리자 선언 */
    delegate void Connect(int a, int b);

    class MainClass
    {
        static void Main(string[] args)
        {
            /* [01]
             * Calculator 클래스의 Plus, Minus 함수 사용 */
            Calculator Calc = new Calculator();
            Calc.Plus(3, 4);
            Calculator.Minus(3, 4);
            Console.WriteLine();

            // 대리자 Callback을 Calculator 클래스의 Plus 함수 대신 사용
            Connect Callback1 = new Connect(Calc.Plus);
            Callback1(10, 5);
            Console.WriteLine();

            Callback1 += Calculator.Minus;
            Callback1(20, 10);
            Console.WriteLine();
            
            Callback1 += Calculator.Minus;
            Callback1(30, 10);

        }
    }
}

 

 

C++ Qt의 Connect 에서 Signal 과 Slot 관계를

C#의 Event 와 EventListener 관계와 동일 개념으로 이해하고 접근해도 된다.

using System;

namespace DelegateTest10
{
    delegate void Callback(string message);

    class Connect
    {
        public Callback? Signal;
    }

    class Label
    {
        private string name;

        public Label(string name)
        {
            this.name = name;
        }

        public void Slot_Function(string message)
        {
            Console.WriteLine($"{name}.SomethingHappened : {message}");
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            // 버튼을 클릭하면, 레이블에 출력된다고 가정합니다.
            // 버튼에 Signal, 레이블에 slot 

            // Signal 대리자를 가진 Connect 객체를 생성합니다.
            Connect conn = new Connect();

            // 레이블을 만들고
            Label lbl = new Label("Label");

            // Signal이 발생하면, 동작할 레이블의 slot 함수와 연결합니다.
            conn.Signal += lbl.Slot_Function;

            // 버튼이 눌렸을 때, Connect 객체의 Signal이발생한다고 가정합니다.
            
            // conn 객체의 Signal 대리자가 실행되면(버튼이 눌리거나, 값이 입력되면)
            // label의 Slot_Function 함수가 "You've got mail."을 전달받아 실행됩니다.
            conn.Signal("You've got mail.");
        }
    }
}

 

 

C++ Qt 의 Signal, Slot 개념과 용어에서 벗어나

Event 와 Event Listener 개념과 용어에 익숙해져 봅시다.

using System;

namespace DelegateTest10
{
    delegate void Callback(string message);

    class Connect
    {
        public Callback? Signal;
    }

    class EventListener
    {
        private string name;

        public EventListener(string name)
        {
            this.name = name;
        }

        public void SomethingHappend(string message)
        {
            Console.WriteLine($"{name}.SomethingHappened : {message}");
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            // 대리자를 가진 Connect 객체
            Connect conn = new Connect();

            // 객체 3개 생성(ex. 레이블 3개 생성, 텍스트 뷰어 3개 생성)
            // @Callback을 전달받아 실행해야 할 객체 생성, 아직 연결 X
            EventListener listener1 = new EventListener("Listener1");
            EventListener listener2 = new EventListener("Listener2");
            EventListener listener3 = new EventListener("Listener3");


            /*[1]
             */
            // conn 객체에 EventListener 객체 3개를 대리자 체인으로 묶음
            // @Callback을 전달받아 실행해야 할 객체의 함수를 대리자에 연결
            conn.Signal += listener1.SomethingHappend;
            conn.Signal += listener2.SomethingHappend;
            conn.Signal += listener3.SomethingHappend;

            // conn 객체의 EventOccured 대리자가 실행되면, 
            // @버튼이 눌리거나, 값이 입력되면 (= 대리자가 실행되면)
            conn.Signal("You've got mail.");
            Console.WriteLine();


            /*[2]
             */
            conn.Signal -= listener2.SomethingHappend;
            conn.Signal("Download complete.");
            Console.WriteLine();


            /*[3]
             */
            conn.Signal = new Callback(listener2.SomethingHappend)
                              + new Callback(listener3.SomethingHappend);
            conn.Signal("Nuclear launch detected.");
            Console.WriteLine();

            /*[4]
             */
            Callback notify1 = new Callback(listener1.SomethingHappend);
            Callback notify2 = new Callback(listener2.SomethingHappend);

            conn.Signal = (Callback)Delegate.Combine(notify1, notify2);
            conn.Signal("Fire!!");
            Console.WriteLine();

            conn.Signal = (Callback)Delegate.Remove(conn.Signal, notify2);
            conn.Signal("RPG!");
        }
    }
}

 

 

 

 

 

 

 

BasicLike

어? 나 프로그래밍 좋아하네?