Изучение позднего связывания с помощью делегатов

Завершено

В C#методы используются для выполнения действий, таких как выполнение вычисления или получение сведений. Приложения используют методы для достижения ожидаемых результатов и вызова методов при выполнении определенных условий. Приложения часто автоматизируют процессы путем вызова одного метода другим, который может вызывать третий метод и т. д. Так как условия известны во время компиляции, исполняемый код реализует предопределенные рабочие процессы предсказуемым образом. Например, у вас может быть метод, который создает ежемесячный отчет о клиенте при нажатии кнопки. При запуске кода и нажатии кнопки вызывается метод и создается отчет. Этот сценарий является примером ранней привязки, где метод, который будет вызван, определяется во время компиляции. Ранняя привязка отлично подходит для многих сценариев, так как это упрощает чтение и понимание кода. Вы можете просмотреть поток программы, выполнив вызовы метода. Код предсказуем, и вы точно знаете, какой метод вызывается при нажатии кнопки.

Однако существует время, когда вы не знаете, какой метод вызывать, пока программа не будет запущена. Например, предположим, что вы работаете над приложением, которое зависит от зависимостей среды выполнения, чтобы решить, какой метод необходим для анализа данных клиента. Этот сценарий является примером поздней привязки, в которой метод определяется в ходе выполнения. В этом случае вызываемая метод может зависеть от входных данных внешнего ресурса или текущего состояния приложения.

В C# делегаты используются для реализации позднего связывания.

Что такое делегат?

Делегат — это тип .NET, производный от Delegate класса и используемый для инкапсулирования методов. Инкапсулируя методы и создавая их в качестве объектов, делегаты позволяют хранить методы в переменных, передавать их в качестве аргументов другим методам и вызывать их в дальнейшем.

Сигнатура делегата определяет параметры и тип возвращаемого значения методов, которые могут быть ему назначены. Это означает, что можно создать тип делегата, соответствующий сигнатуре метода, а затем назначить любой метод с этой подписью делегату. Эта возможность позволяет вызвать метод с помощью делегата, даже если вам неизвестно, какой это метод на этапе компиляции.

В следующем примере показано, как определить тип делегата:


public delegate int PerformCalculation(int x, int y);

В этом примере определяется делегат с именем PerformCalculation. Делегат PerformCalculation определяется для представления методов, которые принимают два int параметра (x и y) и возвращают значение int. Этот делегат можно использовать для инкапсулирования методов, выполняющих различные вычисления, такие как добавление, вычитание, умножение, деление или более сложные уравнения, использующие два параметра.

Например, этот делегат можно использовать для инкапсулирования следующих методов:


public class Calculator
{
    public int Add(int x, int y)
    {
        return x + y;
    }

    public int Subtract(int x, int y)
    {
        return x - y;
    }

    public int Multiply(int x, int y)
    {
        return x * y;
    }

    public int Divide(int x, int y)
    {
        if (y == 0)
            throw new DivideByZeroException();
        return x / y;
    }
}

В этом примере определяется Calculator класс, содержащий методы, соответствующие сигнатуре делегата PerformCalculation . Вы можете создавать экземпляры делегата и назначать их различным методам, позволяя вызывать соответствующий метод во время выполнения в зависимости от ваших потребностей.

В следующем коде показано, как использовать делегат PerformCalculation.


public class Program
{
    public static void Main()
    {
        Calculator calculator = new Calculator();

        // Create delegate instances
        PerformCalculation add = new PerformCalculation(calculator.Add);
        PerformCalculation subtract = new PerformCalculation(calculator.Subtract);
        PerformCalculation multiply = new PerformCalculation(calculator.Multiply);
        PerformCalculation divide = new PerformCalculation(calculator.Divide);

        // Call the methods using the delegates
        Console.WriteLine("Addition: " + add(5, 3)); // Output: 8
        Console.WriteLine("Subtraction: " + subtract(5, 3)); // Output: 2
        Console.WriteLine("Multiplication: " + multiply(5, 3)); // Output: 15
        Console.WriteLine("Division: " + divide(5, 3)); // Output: 1
    }
}

Этот пример создает экземпляр Calculator класса и назначает методы экземплярам делегата PerformCalculation . Затем методы вызываются с помощью экземпляров делегата, что позволяет выполнять различные вычисления на основе назначенного делегата.

Характеристики типа делегата

Делегаты являются ссылочными типами, что означает, что они хранятся в куче и могут передаваться так же, как любой другой объект. При создании экземпляра делегата вы создаете объект, инкапсулирующий метод. Эта инкапсуляция позволяет рассматривать делегат как объект первого класса, как и любой другой объект в C#. Вы можете создавать экземпляры делегатов, присваивать их переменным и передавать их в методы в качестве аргументов. Процесс аналогичен работе с другими объектами в C#, например строками или списками.

Типы делегатов запечатанные, они не могут служить базой для наследования, и невозможно создавать пользовательские классы на основе класса Delegate. Делегаты неизменяемы; это означает, что после создания делегата невозможно изменить метод, который он инкапсулирует. Однако вы можете создавать новые экземпляры делегатов, которые указывают на различные методы. Это неизменяемость гарантирует, что делегат всегда указывает на один и тот же метод, что важно для поддержания целостности кода.

Вы вызываете экземпляр делегата, например метод, и вызывает метод, который он инкапсулирует.

Делегаты имеют следующие характеристики:

  • Делегаты позволяют передавать методы в качестве аргументов другим методам.
  • Делегаты можно связать в цепочку, например, чтобы вызывать несколько методов при обработке одного события.
  • Делегаты типобезопасны, то есть компилятор проверяет, является ли подпись метода подписью делегата во время компиляции.

Замечание

Методы не должны точно соответствовать типу делегата. Если сигнатура метода совместима, компилятор позволяет назначать метод делегату. Это поведение называется ковариантностью и контравариантностью. Ковариация позволяет использовать более производный тип, чем первоначально указанное, в то время как контравариантность позволяет использовать менее производный тип. Эта возможность полезна при работе с наследованием и полиморфизмом, так как позволяет создавать более гибкий и многократно используемый код. Вариативность рассматривается в отдельном уроке этого модуля.

Зачем использовать делегаты?

Делегаты предлагают несколько преимуществ по сравнению с прямыми вызовами методов и решают несколько важных проблем, связанных с гибкостью кода, динамическим вызовом методов и безопасностью типов.

Делегаты предоставляют множество преимуществ, включая следующие элементы:

  • Гибкость. Делегаты позволяют передавать различные методы в качестве параметров, обеспечивая динамическое поведение в зависимости от условий выполнения. Эта гибкость полезна при определении точной операции во время выполнения.
  • Расширяемость. С помощью делегатов можно легко расширить функциональные возможности, добавив новые операции без изменения существующего кода. Вы можете передать любой метод, соответствующий подписи делегата.
  • Разделение: Делегаты разделяют вызов метода и определение метода, делая код более модульным и удобным для поддержки.

Делегаты — это мощная функция C#, которая решает несколько распространенных проблем программирования. Они предоставляют гибкий и типобезопасный способ инкапсуляции методов, что позволяет вызывать динамические методы, осуществлять методы обратного вызова и выполнять мультикаст-вызов. С помощью делегатов можно написать более многократно используемый и обслуживаемый код, который может адаптироваться к изменению требований.

Вызов динамического метода

Возможность динамически вызывать методы во время выполнения — это мощная функция делегатов. Вызов динамического метода позволяет создавать более гибкий и многократно используемый код, так как можно передавать различные методы в качестве параметров, не зная их точной реализации во время компиляции.

Сценарий. Представьте, что у вас есть список клиентов, и вам нужно отсортировать их по разным критериям, таким как имя, тип учетной записи или идентификатор клиента. Без делегатов вам потребуется написать отдельные методы сортировки для каждого критерия, что приводит к повторяющимся и менее поддерживаемым кодам.

Решение. Делегаты позволяют передавать методы в качестве параметров, обеспечивая вызов динамических методов. Вы можете определить делегат, соответствующий сигнатуре метода сортировки, и передать различные функции сравнения во время выполнения. Это делает код более гибким и повторно используемым.

Методы обратного вызова

Потребность в методах обратного вызова возникает в сценариях, когда требуется выполнить действие после завершения определенной операции. Шаблон обратного вызова распространен в асинхронном программировании, где может потребоваться уведомить пользователя или выполнить другие действия после завершения длительной задачи.

Сценарий. В асинхронном программировании часто необходимо выполнять другие действия после завершения операции. Без делегатов вам придется тесно сочетать асинхронную операцию с последующими действиями, уменьшая гибкость и делая код более сложным для поддержания.

Решение: Делегаты позволяют реализовывать функции обратного вызова, что дает возможность динамически указывать последующие действия. Это различает асинхронную операцию от логики обратного вызова, повышая гибкость и удобство обслуживания.

Безопасность типов

Безопасность типов — это важный аспект программирования, который гарантирует, что вызываемые методы соответствуют ожидаемым сигнатурам. Это предотвращает ошибки среды выполнения и делает код более надежным.

Сценарий. При передаче методов в качестве параметров или их хранения для последующего вызова необходимо убедиться, что сигнатуры метода соответствуют. Без делегатов вам не хватает безопасности типов, что приводит к потенциальным ошибкам среды выполнения и менее надежному коду.

Решение: Делегаты предоставляют типобезопасные ссылки на методы, тем самым гарантируя соответствие сигнатур методов сигнатуре делегата. Это предотвращает ошибки среды выполнения и делает код более надежным.

Вызов многоадресной рассылки

Вызов с многократным делегированием — это функция делегатов, которая позволяет вызывать несколько методов с помощью одного делегата. Эта функция полезна в сценариях обработки событий, где вы хотите уведомить нескольких слушателей событий о происшествии.

Сценарий. В обработке событий часто требуется уведомить нескольких подписчиков о возникновении. Без делегатов необходимо вручную управлять списком подписчиков и вызывать их методы, что приводит к сложному и ошибкам коду.

Решение: Делегаты поддерживают многоадресный вызов, позволяя одному делегату ссылаться на несколько методов. Это упрощает обработку событий, автоматически управляя списком подписчиков и вызывая методы в последовательности.

Рекомендации по объявлению делегатов в C#

При добавлении делегатов в класс важно следовать рекомендациям, чтобы убедиться, что код упорядочен, доступен для чтения и обслуживания. Ниже приведены некоторые рекомендации по определению делегатов:

Определение делегатов в верхней части файла класса

Обычно разработчики объявляют делегаты в верхней части файла, как правило, в пространстве имен, но за пределами любого класса. Этот подход упрощает поиск определений делегатов, а также понимание их назначения. Он также позволяет определить делегаты, которые могут использоваться несколькими классами в одном пространстве имен.

В следующем коде показано, как реализовать делегат, определенный за пределами класса:


namespace MyNamespace

// Define the delegate outside the class
public delegate void MyDelegate(string message);

public class Publisher
{
    // Method that uses the delegate
    public void PublishMessage(MyDelegate del)
    {
        del("Hello from Publisher!");
    }
}

public class Subscriber
{
    public void Subscribe()
    {
        // Create an instance of the Publisher class
        Publisher publisher = new Publisher();

        // Create an instance of the delegate and pass a method to it
        MyDelegate del = new MyDelegate(PrintMessage);

        // Call the method of the Publisher class and pass the delegate
        publisher.PublishMessage(del);
    }

    // Method that matches the delegate signature
    public void PrintMessage(string message)
    {
        Console.WriteLine(message);
    }
}

Определение делегатов в классе

Если делегат относится к классу и не предназначен для использования за пределами класса, можно определить делегат в классе. Это включает в себя делегат в классе, что полезно, если делегат тесно связан с функциями класса и не предназначен для использования за пределами этого контекста. Если делегат является общедоступным, другие классы в том же пространстве имен могут получить доступ к делегату с помощью экземпляра класса.


namespace MyNamespace

public class Publisher
{
    // Define a public delegate
    public delegate void MyDelegate(string message);

    // Method that uses the delegate
    public void PublishMessage(MyDelegate del)
    {
        del("Hello from Publisher!");
    }
}

public class Subscriber
{
    public void Subscribe()
    {
        // Create an instance of the Publisher class
        Publisher publisher = new Publisher();

        // Create an instance of the delegate and pass a method to it
        Publisher.MyDelegate del = new Publisher.MyDelegate(PrintMessage);

        // Call the method of the Publisher class and pass the delegate
        publisher.PublishMessage(del);
    }

    // Method that matches the delegate signature
    public void PrintMessage(string message)
    {
        Console.WriteLine(message);
    }
}


Ключевые моменты

  • Делегаты — это типы .NET, производные от Delegate класса, который инкапсулирует методы.
  • Делегаты позволяют передавать методы в качестве параметров, хранить их в переменных и вызывать их во время выполнения.
  • Делегаты являются типобезопасными, то есть компилятор проверяет, совпадают ли подписи метода с подписью делегата во время компиляции.
  • Делегаты можно использовать для вызова динамических методов, методов обратного вызова, обеспечения безопасности типов и многоадресного вызова.
  • Делегаты являются основой для обработки событий в C#.
  • Делегаты можно объединить в цепочку, что позволяет вызывать несколько методов с помощью одного делегата.
  • Делегаты обычно определяются вне класса, чтобы разрешить доступ из других классов без необходимости создавать экземпляры класса.