Monday, February 21, 2011

Делегаты

В C# нет указателей (про unsafe я молчу). И это хорошо и правильно – указатель он по сути своей не типизирован. Передали в метод ссылку, а как ее интерпретировать – на совести принимающей стороны. Можем передать int, а использовать как float. И т.д. Тоже самое касается и методов – передали указатель на метод с одной сигнатурой, а используем совсем по-другому…
Зато в C# есть делегаты. Я бы назвал делегаты “типизированные указатели на методы”. Покажу на примере как это работает.
Сначала нужно описать тип делегата, т.е. сигнатуру метода. Описание можно не привязывать к конкретному классу:
namespace DelegateTest
{
    public delegate decimal GetVarValue(string name);

Здесь метод имеет один параметр типа string и возвращает decimal.
Теперь этот делегат мы можем использовать при описании параметров и переменных:
public class Parser
{
    /// <summary>
    /// Описание переменной, сохращающей делегат
    /// </summary>
    private GetVarValue getVarValueHandler;

    /// <summary>
    /// Передаем делегат в конструктор
    /// </summary>
    /// <param name="getVarValueHandler"></param>
    public Parser(GetVarValue getVarValueHandler)
    {
        // Сохраняем делегат, т.к. хотим использовать его
        // в другом методе этого класса
        this.getVarValueHandler = getVarValueHandler;
    }

    /// <summary>
    /// Метод расчета
    /// </summary>
    /// <returns></returns>
    public decimal Calc()
    {
        decimal result = 0;

        // Вызываем делегат с параметром var1
        result += getVarValueHandler("var1");
        // Вызываем делегат с параметром var2
        result += getVarValueHandler("var2");

        return result;
    }

Описание переменной типа делегат выглядит совершенно так же как и обычной переменной. Переменная getVarValueHandler имеет тип GetVarValue, т.е. тип делегата с нашей сигнатурой. Отличие от переменной в том, что мы можем вызывать этот метод. Разумеется, вызывать его можно только так, как он описан, т.е. с одним параметром типа string:
                 getVarValueHandler("var1");
Прелесть  в том, что никак иначе вызвать метод по этой ссылке (а передали мы фактически ссылку на метод) нельзя. Только так, как метод описан в описании делегата.
Использовать этот класс тоже очень просто. Во-первых, нам потребуется метод, который мы будем передавать в конструктор нашего класса. Наверное, излишне говорить, что сигнатура этого метода должна совпадать с сигнатурой делегата и передать в конструктор что-то другое нельзя:
class Program
{
    static void Main(string[] args)
    {
        // Передаем в конструктор делегат, т.е. ссылку
        // на наш метод
        Parser parser = new Parser(GetVarValueMethod);
        // Вызываем метод Calc
        Console.WriteLine(parser.Calc());
    }

    /// <summary>
    /// Делегат. Имя у него может быть любое, главное
    /// чтобы сигнатура была той как описана в типе
    /// делегата
    /// </summary>
    static decimal GetVarValueMethod(string name)
    {
        Console.WriteLine("Value of {0}:", name);
        return decimal.Parse(Console.ReadLine());
    }

}

Имя метода может быть может быть любое, главное чтобы сигнатура совпадала с сигнатурой делегата.
Вот и все. Теперь при вызове метода Calc будет два раза вызван метод GetVarValueMethod.

No comments: