Знакомство с делегатами в C# (Часть 2)

Делегаты в C# (практика)

Доброго времени суток! В этой статье, я покажу как на практике можно использовать делегаты, и как с их помощью добиваться, в некотором смысле, универсальности (или абстракции) поведения для объектов своих классов. Эта статья является продолжением предыдущей, так что рекомендую её почитать, если Вы совсем не знакомы с делегатами в C#. И так, давайте вспомним что такое делегат… По сути, это объект, который хранит ссылку на некий метод, и может этот метод вызвать при необходимости. Точнее мы можем вызвать метод через этот объект. И особенность использования делегатов в том, что при объявлении ссылки на объект-делегат мы можем и не знать, на какой конкретно метод, будет ссылаться этот объект. Мы только знаем, что целевой метод должен иметь определенного вида список параметров и тип возвращаемого значения. А вот уже эту особенность можно использовать на своё благо! А как это сделать, я сейчас покажу на примере.

Давайте сразу рассмотрим фрагмент кода, в котором представлено объявление типа делегатов и класс, описывающий объекты-коллекции (особо интересные строки выделены):

//Делегат, для использования в классе DoubleCollection
delegate double DoubleOperation(double anAgr);

//Коллекция элементов типа double
class DoubleCollection
{ 
    //Добавляет элемент в коллекцию
    public void Add(double anItem)
    {
        collection.Add(anItem);
    }

    //Удаляет элемент из коллекции (по индексу)
    public void RemoveAt(int anIndex)
    {
        collection.RemoveAt(anIndex);
    }

    //Вызывает метод, на который ссылается делегат для всех элементов коллекции
    public void DoOperation()
    {
        //Если делегат задан
        if (operation != null)
        {
            //Перебрать все элементы коллекции
            for (int i = 0; i < collection.Count; i++)
            {
                //Выполнить операцию над текущим элементом
                collection[i] = operation(collection[i]);
            }
        }
    }

    //Устанавливает или возвращает значение делегата operation 
    public DoubleOperation Operation
    {
        get { return operation; }
        set { operation = value; }
    }

    //Индексатор (возвращает или устанавливает значение элемента по индексу)
    public double this[int anIndex]
    {
        get { return collection[anIndex]; }
        set { collection[anIndex] = value; }
    }

    //Возвращает количество элементов
    public int Count 
    { 
        get
        {
            return collection.Count;
        }
    }

    //Хранилище элементов
    private List<double> collection = new List<double>();
    //Делегат, для хранения ссылки на метод, который укажет пользователь класса
    private DoubleOperation operation = null; 
}

В строке № 2 объявляется тип делегатов «DoubleOperation», делегаты такого типа ссылаются на методы, принимающие один аргумент типа double и возвращающие значение такого же типа. В строке № 60, в классе «DoubleCollection» объявляется поле «operation», которое является ссылкой на делегат, типа «DoubleOperation». Значение полю «operation» задается через свойство «Operation» (строка № 35), но сама суть кроется в методе «DoOperation», объявленном в строке № 20. Рассмотрим подробнее код этого метода:

//Вызывает метод, на который ссылается делегат для всех элементов коллекции
public void DoOperation()
{
    //Если делегат задан
    if (operation != null)
    {
        //Перебрать все элементы коллекции
        for (int i = 0; i < collection.Count; i++)
        {
            //Выполнить операцию над текущим элементом
            collection[i] = operation(collection[i]);
        }
    }
}

В данном методе, перебираются все элементы коллекции, и над каждым выполняется вызов метода, на который ссылается делегат «operation» (см. выделенную строку). Таким образом, мы написали как бы некий шаблон, план действий, т.е. перебрать все элементы и вызвать на каждом метод, но саму суть того метода, задаем не мы, а пользователь класса. Например, он может захотеть увеличить все элементы коллекции на 13 %, или уменьшить вдвое. На этапе разработки класса «DoubleCollection» мы этого не знаем, но предусмотрели возможность пользователю самому указать действия, которые он хочет выполнить над каждым элементом коллекции.

На практике, ему нужно будет создать метод, подходящий по списку параметров и возвращаемому значению к делегату «DoubleOperation», создать на его основе делегат, присвоить его объекту-коллекции через свойство «Operation», и вызвать метод «DoOperation». Это может выглядеть примерно так:

//Основной класс программы
class Program
{
    //Возвращает значение операнда, увеличенное на 13 процентов
    static double SomeOperation(double aValue)
    {
        return aValue * 1.13;
    }

    //Главный метод программы
    static void Main(string[] args)
    {
        //Создание коллекции
        DoubleCollection someCollection = new DoubleCollection();

        //Добавление в неё элементов
        someCollection.Add(124.4);
        someCollection.Add(120.8);
        someCollection.Add(118.1);
        someCollection.Add(128.9);
        someCollection.Add(111.3);

        //Создание делегата на основе метода SomeOperation и присваивание его коллекции
        someCollection.Operation = new DoubleOperation(SomeOperation);

        //Выполнение операции
        someCollection.DoOperation();

        //Вывод результата в консоль
        for (int i = 0; i < someCollection.Count; i++)
           Console.WriteLine(someCollection[i]);
    }
}

Как видите, нет ничего сложного. Пользователю класса «DoubleCollection» нужно просто позаботиться о создании метода «SomeOperation», который мог бы делать и какие-то другие действия. А при использовании анонимных методов или лямбда-выражений (о которых я еще расскажу в следующих статьях), пользователю класса «DoubleCollection» было бы еще удобнее (не пришлось бы создавать отдельный метод в главном классе приложения). Ну а пока — всё!

Добавить комментарий