대리자 형식에 대해 설명하며, 대리자를 정적 메서드와 인스턴스 메서드로 매핑하는 방법과 두 메서드를 결합하는 방법(멀티캐스트)을 보여 줍니다.
설명
C#에서 대리자는 C나 C++에서의 함수 포인터와 유사합니다. 대리자를 사용하면 프로그래머가 대리자 개체 안에서 메서드 참조를 캡슐화할 수 있습니다. 그렇게 하면 메서드를 호출할 컴파일 타임을 몰라도 참조된 메서드를 호출할 수 있는 코드로 대리자 개체를 전달할 수 있습니다. 그러나 C나 C++의 함수 포인터와 달리 대리자는 개체 지향적이고 형식이 안전하며 보안을 설정할 수 있습니다.
대리자 선언은 특정한 인수 및 반환 형식의 집합으로 메서드를 캡슐화하는 형식을 정의합니다. 대리자 개체는 정적 메서드인 경우 호출할 메서드를 캡슐화하고 인스턴스 메서드인 경우에는 인스턴스와 인스턴스의 메서드를 모두 캡슐화합니다. 대리자 개체와 해당 인수 집합이 있는 경우 인수로 대리자를 호출할 수 있습니다.
대리자가 참조하는 개체의 클래스를 모르거나 신경 쓰지 않는다는 점은 흥미롭고 유용한 대리자의 속성입니다. 메서드의 인수 형식과 반환 형식이 대리자의 해당 형식과 일치하기만 하면 모든 개체가 가능하기 때문에 대리자는 "익명" 호출에 가장 적합합니다.
참고 대리자는 선언자의 사용 권한이 아닌 호출자의 보안 권한으로 실행됩니다.
이 자습서에는 다음 두 예제가 들어 있습니다.
- 예제 1에서는 대리자 선언, 인스턴스화 및 호출 방법을 보여 줍니다.
- 예제 2에서는 두 대리자를 결합하는 방법을 보여 줍니다.
또한 다음 항목에 대해서도 설명합니다.
- 대리자 및 이벤트
- 대리자 대 인터페이스
예제 1
다음 예제에서는 대리자 선언, 인스턴스화 및 사용에 대해 설명합니다.BookDB
클래스는 설명서 데이터베이스를 관리하는 설명서 저장소 데이터베이스를 캡슐화합니다. 데이터베이스에서 모든 페이퍼백 설명서를 찾아 각 설명서에 대해 대리자를 호출하는ProcessPaperbackBooks
메서드를 표시합니다. 사용된delegate형식은ProcessBookDelegate
입니다.Test
클래스는 이 클래스를 사용하여 페이퍼백 설명서의 평균 가격과 제목을 인쇄합니다.
대리자를 사용하면 설명서 저장소 데이터베이스와 클라이언트 코드 사이의 기능을 잘 분리할 수 있도록 도와 줍니다. 클라이언트 코드는 설명서 저장 방법이나 설명서 저장소 코드의 페이퍼백 설명서 검색 방법을 알지 못합니다. 설명서 저장소 코드는 페이퍼백 설명서를 검색한 다음 어떤 프로세스를 실행하는지 알지 못합니다.
// bookstore.cs
using System;
// A set of classes for handling a bookstore:
namespace Bookstore
{
using System.Collections;
// Describes a book in the book list:
public struct Book
{
public string Title; // Title of the book.
public string Author; // Author of the book.
public decimal Price; // Price of the book.
public bool Paperback; // Is it paperback?
public Book(string title, string author, decimal price, bool paperBack)
{
Title = title;
Author = author;
Price = price;
Paperback = paperBack;
}
}
// Declare a delegate type for processing a book:
public delegate void ProcessBookDelegate(Book book);
// Maintains a book database.
public class BookDB
{
// List of all books in the database:
ArrayList list = new ArrayList();
// Add a book to the database:
public void AddBook(string title, string author, decimal price, bool paperBack)
{
list.Add(new Book(title, author, price, paperBack));
}
// Call a passed-in delegate on each paperback book to process it:
public void ProcessPaperbackBooks(ProcessBookDelegate processBook)
{
foreach (Book b in list)
{
if (b.Paperback)
// Calling the delegate:
processBook(b);
}
}
}
}
// Using the Bookstore classes:
namespace BookTestClient
{
using Bookstore;
// Class to total and average prices of books:
class PriceTotaller
{
int countBooks = 0;
decimal priceBooks = 0.0m;
internal void AddBookToTotal(Book book)
{
countBooks += 1;
priceBooks += book.Price;
}
internal decimal AveragePrice()
{
return priceBooks / countBooks;
}
}
// Class to test the book database:
class Test
{
// Print the title of the book.
static void PrintTitle(Book b)
{
Console.WriteLine(" {0}", b.Title);
}
// Execution starts here.
static void Main()
{
BookDB bookDB = new BookDB();
// Initialize the database with some books:
AddBooks(bookDB);
// Print all the titles of paperbacks:
Console.WriteLine("Paperback Book Titles:");
// Create a new delegate object associated with the static
// method Test.PrintTitle:
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(PrintTitle));
// Get the average price of a paperback by using
// a PriceTotaller object:
PriceTotaller totaller = new PriceTotaller();
// Create a new delegate object associated with the nonstatic
// method AddBookToTotal on the object totaller:
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(totaller.AddBookToTotal));
Console.WriteLine("Average Paperback Book Price: ${0:#.##}",
totaller.AveragePrice());
}
// Initialize the book database with some test books:
static void AddBooks(BookDB bookDB)
{
bookDB.AddBook("The C Programming Language",
"Brian W. Kernighan and Dennis M. Ritchie", 19.95m, true);
bookDB.AddBook("The Unicode Standard 2.0",
"The Unicode Consortium", 39.95m, true);
bookDB.AddBook("The MS-DOS Encyclopedia",
"Ray Duncan", 129.95m, false);
bookDB.AddBook("Dogbert’s Clues for the Clueless",
"Scott Adams", 12.00m, true);
}
}
}
출력
Paperback Book Titles:
The C Programming Language
The Unicode Standard 2.0
Dogbert’s Clues for the Clueless
Average Paperback Book Price: $23.97
코드 설명
public delegate void ProcessBookDelegate(Book book);
이 문에서는 새 대리자 형식을 선언합니다. 각 대리자 형식은 인수의 형식과 수 그리고 캡슐화할 수 있는 메서드 반환 값의 형식에 대해 설명합니다. 인수 형식이나 반환 값 형식의 새 집합이 필요할 때마다 새 대리자 형식을 선언해야 합니다.
다음
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(PrintTitle));
문은 정적 메서드Test.PrintTitle
과 결합된 새 대리자 개체를 만듭니다. 다음
bookDB.ProcessPaperbackBooks(new ProcessBookDelegate(totaller.AddBookToTotal));
문은totaller
개체에서 비정적 메서드AddBookToTotal
과 결합된 새 대리자 개체를 만듭니다. 두 가지 경우 모두에서 이들 새 대리자 개체는 즉시ProcessPaperbackBooks
메서드로 전달됩니다.
일단 대리자를 만들면 이와 결합된 메서드는 변하지 않으며 대리자 개체는 변경할 수 없습니다.
processBook(b);
대리자는 이 예제에서와 같이 동기적으로 호출할 수도 있고 BeginInvoke 및 EndInvoke 메서드를 사용하여 비동기적으로 호출할 수도 있습니다.
예제 2
이 예제에서는 대리자 작성에 대해 설명합니다. 대리자 개체의 유용한 속성으로는 "+" 연산자를 사용하여 작성할 수 있다는 점을 들 수 있습니다. 작성된 대리자는 자신이 작성된 두 개의 원본 대리자를 호출합니다. 형식이 같은 대리자만 작성에 사용할 수 있습니다.
"–" 연산자를 사용하면 작성된 대리자에서 구성 요소 대리자를 제거할 수 있습니다.
// compose.cs
using System;
delegate void MyDelegate(string s);
class MyClass
{
public static void Hello(string s)
{
Console.WriteLine(" Hello, {0}!", s);
}
public static void Goodbye(string s)
{
Console.WriteLine(" Goodbye, {0}!", s);
}
public static void Main()
{
MyDelegate a, b, c, d;
// Create the delegate object a that references
// the method Hello:
a = new MyDelegate(Hello);
// Create the delegate object b that references
// the method Goodbye:
b = new MyDelegate(Goodbye);
// The two delegates, a and b, are composed to form c:
c = a + b;
// Remove a from the composed delegate, leaving d,
// which calls only the method Goodbye:
d = c – a;
Console.WriteLine("Invoking delegate a:");
a("A");
Console.WriteLine("Invoking delegate b:");
b("B");
Console.WriteLine("Invoking delegate c:");
c("C");
Console.WriteLine("Invoking delegate d:");
d("D");
}
}
출력
Invoking delegate a:
Hello, A!
Invoking delegate b:
Goodbye, B!
Invoking delegate c:
Hello, C!
Goodbye, C!
Invoking delegate d:
Goodbye, D!
대리자 이벤트
대리자는 한 구성 요소의 변경 사항에 대해 해당 구성 요소에서 "수신자"로 알림을 통지하는 이벤트로 사용하기에 매우 적합합니다.
대리자 vs 인터페이스
대리자와 인터페이스는 사양과 구현 분리가 가능하다는 점에서 유사합니다. 각기 독립적인 여러 명의 만든 이가 인터페이스 사양과 호환되는 구현을 만들 수 있습니다. 이와 유사하게 대리자가 메서드 시그니처를 지정하고 만든 이가 대리자 사양과 호환되는 메서드를 작성할 수 있습니다. 인터페이스와 대리자 사용 시기는 다음과 같습니다.
다음과 같은 경우에는 대리자가 유용합니다.
- 단일 메서드가 호출되는 경우
- 클래스에서 메서드 사양을 다중 구현하려는 경우
- 사양 구현에 정적 메서드 사용이 바람직한 경우
- 이벤트형 디자인 패턴이 바람직한 경우
- 호출자가 메서드가 정의된 개체를 알거나 구할 필요가 없는 경우
- 구현 공급자가 사양 구현을 선택된 일부 구성 요소에만 "배포"하려는 경우
- 쉽게 작성하려는 경우
다음과 같은 경우에는 인터페이스가 유용합니다.
- 사양에서 호출할 관련 메서드 집합을 정의한 경우
- 클래스가 일반적으로 한 번만 사양을 구현하는 경우
- 인터페이스 호출자가 다른 인터페이스나 클래스를 구하기 위해 인터페이스 형식 간에 변환하려는 경우