C# 프로그래밍 - CH11 델리게이터와 람다

sookyeong·2022년 3월 30일
0

Preview

현대 프로그래밍 언어들은 메서드라는 행위를 변수처럼 활용하는 기능이 있음

이는 메서드 자체를 매개변수로 전달하거나 반환할 수 있다는 말이고, 연산자까지 활용할 수 있게 된다는 말임

C#은 이러한 것을 델리게이터로 구현했음


델리게이터 관련 용어

메서드는 행위를, 변수는 데이터(값)를 나타냄

변수는 메서드와 달리 굉장히 유연해 매개변수 등으로 이리저리 전달할 수 있음

우리 삶에서도 물질적인 것은 누군가에게 전달할 수 있지만 행위는 전달할 수가 없음

행위를 저장하고 전달할 수 있다면 굉장히 편리하겠다는 생각으로 메서드를 변수로 사용하는 개념이 프로그래밍 언어에 등장하기 시작함

C#은 델리게이터와 람다라는 개념으로 이를 구현함

델리게이터(≠무명 델리게이터)는 형식화된 메서드로, 일종의 클래스를 선언하는 것과 같음

그리고 이것을 변수로 만들어 초기화할 때 사용하는 것이 메서드 이름, 무명 델리게이터, 람다임

delegate void TestDelegate();   // 델리게이터 = 자료형을 선언하는 방법

TestDelegate testDelegate = <메서드 이름, 무명 델리게이터, 람다>    // 델리게이터 변수를 초기화

델리게이터 기본

다음은 델리게이터를 사용한 정렬 구현 예제임

class Program
{
    class Product
    {
        public string Name { get; set; }
        public int Price { get; set; }
    }

    static void Main(string[] args)
    {
        List<Product> products = new List<Product>()
        {
            new Product() { Name = "감자", Price = 500 },
            new Product() { Name = "사과", Price = 700 },
            new Product() { Name = "고구마", Price = 400 },
            new Product() { Name = "배추", Price = 600 },
            new Product() { Name = "상추", Price = 300 }
        };

        products.Sort(SortWithPrice);   // Comparison 델리게이터

        foreach (var item in products)
        {
            Console.WriteLine(item.Name + " : " + item.Price);
        }
    }

    static int SortWithPrice(Product a, Product b)
    {
        return a.Price.CompareTo(b.Price);
    }
}

Comparison 델리게이터의 메서드 형식은 마이크로소프트에서 제공하는 관련 다큐멘테이션을 참고하면 됨

public delegate int Comparison<in T>(T x, T y);

정렬할 때마다 정렬의 종류만큼 메서드를 만들어줄 수는 없으므로 이러한 코드를 간단하게 만들고자 무명 델리게이터를 사용함


무명 델리게이터 기본

delegate(<매개변수>, <매개변수>) {
    /* 코드 */
    return /* 반환 */ ;
}

정렬 예제의 델리게이터를 무명 델리게이터로 대체함

static void Main(string[] args)
{
    List<Product> products = new List<Product>()
    {
        new Product() { Name = "감자", Price = 500 },
        new Product() { Name = "사과", Price = 700 },
        new Product() { Name = "고구마", Price = 400 },
        new Product() { Name = "배추", Price = 600 },
        new Product() { Name = "상추", Price = 300 }
    };

    products.Sort(delegate(Product a, Product b)    // 무명 델리게이터로 정렬 방법을 정의
    {
        return a.Price.CompareTo(b.Price);
    });

    foreach (var item in products)
    {
        Console.WriteLine(item.Name + " : " + item.Price);
    }
}

람다 기본

델리게이터를 조금 더 편하게 사용할 수 있게 해주는 것이 람다임

델리게이터를 짧게 쓴다고 생각하면 됨

매개변수의 자료형을 지정할 필요도 없음

(<매개변수>, <매개변수>) => {
    /* 코드 */
    return /* 반환 */;
}

만약 메서드 내부에 입력할 코드가 딱히 없다면 다음과 같이 간략화할 수 있음

(a, b) => /* 반환 */

정렬 예제의 무명 델리게이터를 람다로 대체함

static void Main(string[] args)
{
    List<Product> products = new List<Product>()
    {
        new Product() { Name = "감자", Price = 500 },
        new Product() { Name = "사과", Price = 700 },
        new Product() { Name = "고구마", Price = 400 },
        new Product() { Name = "배추", Price = 600 },
        new Product() { Name = "상추", Price = 300 }
    };

    products.Sort((a, b) =>    // Sort() 메서드의 매개변수로 람다를 지정
    {
        return a.Price.CompareTo(b.Price);
    });

    foreach (var item in products)
    {
        Console.WriteLine(item.Name + " : " + item.Price);
    }
}

해당 람다를 다음과 같이 간략화할 수 있음

products.Sort((a, b) => a.Price.CompareTo(b.Price));

즉, 델리게이터는 메서드를 쉽게 사용할 수 있는 형태라고 생각하면 됨


클로저

원래 지역 변수는 메서드 내부에서 선언되어 메서드가 종료될 때 사라지는 것이 정석이나, 델리게이터와 람다를 사용하게 되면 이러한 규칙에 위배되는 경우가 생김

이처럼 지역 변수가 메서드가 끝나도 사라지지 않고 남는 현상을 클로저(Closer)라고 부르며, 클로저가 일어난 변수를 클로저 변수라고 부름


이름 있는 델리게이터

위에서 살펴본 무명 델리게이터는 자신의 원하는 형식으로 자유롭게 선언할 수 있는 델리게이터임

반대로 이름이 있는 델리게이터를 그냥 델리게이터라고 부르는데, 이러한 델리게이터에는 정해진 형식이 있음

무명 델리게이터와 이름 있는 델리게이터는 카테고리 자체가 다르므로 둘을 확실하게 구분해야 함


델리게이터 선언

접근 제한자 delegate 반환형 델리게이터 이름(매개변수);

이름 있는 델리게이터는 특정한 형식을 가진 메서드를 자료형으로 선언하는 방법임

델리게이터는 자료형이므로 클래스를 선언하는 위치와 같은 위치라면 어디서든지 선언할 수 있음

사실 위와 같이 델리게이터를 선언하면, Delegate 클래스의 상속을 받는 클래스를 선언하는 것임

많은 C# 개발자가 델리게이터를 멤버로 취급하여 멤버를 선언하는 위치에 선언하는데, 클래스 외부에도 선언할 수 있다는 점을 기억해야 함


델리게이터 선언 위치

public delegate void TestDelegateA();   // 클래스 외부

class Program
{
    public delegate void TestDelegateB();   // 클래스 내부

    static void Main(string[] args)
    {
        TestDelegateA delegateA;    // 선언한 델리게이터 자료형으로 변수 선언
        TestDelegateB delegateB;
    }
}

델리게이터 자료형 변수 초기화

위에서 살펴보았던 세 가지 방법을 사용하여 초기화할 수 있음

class Program
{
    public delegate void TestDelegate();

    static void Main(string[] args)
    {
        TestDelegate delegateA = TestMethod;    // 메서드 이름
        TestDelegate delegateB = delegate() { };    // 무명 델리게이터
        TestDelegate delegateC = () => { };     // 람다

        delegateA();    // 델리게이터는 일반 메서드처럼 호출할 수 있음
        delegateB();
        delegateC();
    }

    static void TestMethod()
    {

    }
}

델리게이터 활용

델리게이터를 활용하는 대표적인 형태는 콜백 메서드(Callback Method)임

콜백 메서드 = 매개변수로 전달하는 메서드

기본적인 형태

public delegate void CustomDelegate();

public void Method(CustomDelegate customDelegate)
{
    CustomDelegate();
}

델리게이터 PrintProcess를 선언 후 콜백 메서드로 활용하는 예제

class Student
{
    public string Name { get; set; }
    public double Score { get; set; }

    public Student(string name, double score)
    {
        this.Name = name;
        this.Score = score;
    }

    public override string ToString()
    {
        return this.Name + " : " + this.Score;
    }
}

class Students
{
    private List<Student> listOfStudent = new List<Student>();

    public delegate void PrintProcess(Student list);

    public void Add(Student student)
    {
        listOfStudent.Add(student);
    }

    public void Print()
    {
        Print((student) =>
        {
            Console.WriteLine(student);
        });
    }

    public void Print(PrintProcess process)
    {
        foreach (var item in listOfStudent)
        {
            process(item);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        Students students = new Students();
        students.Add(new Student("염수경", 4.2));
        students.Add(new Student("염수연", 4.3));

        students.Print();
        students.Print((student) =>
        {
            Console.WriteLine();
            Console.WriteLine("이름: " + student.Name);
            Console.WriteLine("학점: " + student.Score);
        });
    }
}
염수경 : 4.2
염수연 : 4.3

이름: 염수경
학점: 4.2

이름: 염수연
학점: 4.3

다시 한번 내 언어로 정리하기

델리게이트는 메서드를 다른 메서드의 매개변수로 전달하기 위한 일종의 껍질이다.

알맹이인 메서드와 껍질인 델리게이트는 매개변수의 타입 및 갯수, 반환형의 타입이 모두 일치해야한다.

델리게이트의 선언 형태는 마치 함수를 정의하는 선언식처럼 보이지만 내부적으로는 특별한 클래스로 변환된다.

따라서 선언한 델리게이트를 사용할 때는 new 키워드로 델리게이트 객체를 생성하여 사용한다.


델리게이터 연산

델리게이터에 연산자를 적용할 수 있는 언어는 C#이 거의 유일함

즉, 없어도 프로그래밍하는데 지장은 없는 기능임

하지만 있으면 나름 편리하며 C#의 기능은 대부분 이미 델리게이터 연산을 사용해 구현되어 있으므로 사용하지 않을 수는 없음

일반 연산자와 마찬가지로 + 로 델리게이터를 더하거나, - 로 델리게이터를 뺄 수 있음

델리게이터 덧셈과 뺄셈

class Program
{
    public delegate void SendString(string message);

    static void Main(string[] args)
    {
        SendString sayHello, sayGoodbye, multiDelegate;

        sayHello = Hello;
        sayGoodbye = GoodBye;

        multiDelegate = sayHello + sayGoodbye;
        multiDelegate("염수경");

        Console.WriteLine();

        multiDelegate -= sayGoodbye;
        multiDelegate("염수경");
    }

    public static void Hello(string message)
    {
        Console.WriteLine("안녕하세요. " + message + "씨...");
    }

    public static void GoodBye(string message)
    {
        Console.WriteLine("안녕히 가세요. " + message + "씨...");
    }
}
안녕하세요. 염수경씨...
안녕히 가세요. 염수경씨...

안녕하세요. 염수경씨...

스레드 생성하기

Thread 클래스를 사용함

Thread 클래스의 매개변수에는 메서드 이름, 무명 델리게이터, 람다를 넣을 수 있음

class Program
{
    static void Main(string[] args)
    {
        Thread threadA = new Thread(TestMethod);    // 메서드 이름
        Thread threadB = new Thread(delegate()      // 무명 델리게이터
        {

        });
        Thread threadC = new Thread(() =>           // 람다
        {

        });
    }

    public static void TestMethod()
    {

    }
}

이렇게 만들어진 스레드는 Start() 메서드로 실행함

다음은 람다를 사용해 스레드 3개를 생성하고 실행하는 예제임

static void Main(string[] args)
{
    Thread threadA = new Thread(() =>
    {
        for(int i=0; i<1000; i++)
        {
            Console.Write("A");
        }
    });
    Thread threadB = new Thread(() =>
    {
        for(int i=0; i<1000; i++)
        {
            Console.Write("B");
        }
    });
    Thread threadC = new Thread(() =>
    {
        for(int i=0; i<1000; i++)
        {
            Console.Write("C");
        }
    });

    threadA.Start();
    threadB.Start();
    threadC.Start();
}
profile
actions speak louder than words

0개의 댓글