Делегаты и анонимные функции

В версии языка C# 1.0 объявить делегат можно было только с помощью именованных методов. Способ вызова методов с использованием объектов (экземпляров) делегата описан в  статье Делегаты и методы. Вспомнив, по-видимому, что в С++ используются встроенные (in-line) функции, разработчики языка C# добавили их аналог в виде анонимных функций. В версии языка C# 2.0 (2005 год) появились анонимные методы, а в C# 3.0  (2008 год) — лямбда-выражения, которые теперь рекомендуется использовать для написания встроенного кода. Рассмотрим их более подробно.

Пример:

using System;
namespace АнонимныеФункции
{
class Program
{
   // делегат ("шаблон")
   delegate void StringDelegate(string s);

   // метод для 1 варианта (C# 1.0)
   static void cout(string s)
   {
      Console.WriteLine(s);
   }

   // другой метод
   static void cout_2(string s)
   {
          Console.WriteLine("Вывод с предисловием: " + s);
   }
   
   static void Main(string[] args)
   { 
      // Явная инициализация делегата через имя метода (C# 1.0)
      StringDelegate DEL_1 = new StringDelegate(cout);

      // Анонимный метод вызывается через делегат (C# 2.0)
      StringDelegate DEL_2 = delegate(string s) { Console.WriteLine(s); };

      // Делегат вызывает лямбда-функцию (C# 3.0)
      StringDelegate DEL_3 = (x) => { Console.WriteLine(x); };

      // Применение делегатов
      cout("Три способа использования делегатов:");
      DEL_1("C# 1.0 - через имя метода");
      DEL_2("C# 2.0 - через анонимный метод");
      DEL_3("C# 3.0 - через лямбда-функцию");
  
      // связать с другим методом
      DEL_1 = new StringDelegate(cout_2);
      DEL_1("C# 1.0 - через имя метода");

      Console.ReadKey();
   }
 }
}

Результат:

Три способа использования делегатов:
C# 1.0 — через имя метода
C# 2.0 — через анонимный метод
C# 3.0 — через лямбда-функцию
Вывод с предисловием: C# 1.0 — через имя метода

Анализ:

1) Если ваше действие состоит только в выводе строки, то используйте метод cout(s) (1 строка вывода) вместо более длинного оператора Console.WriteLine(s) и забудьте о делегатах.

2) Использование экземпляра делегата DEL_1 через имя метода (2 и 5 строки) позволяет изменять метод в разных частях программного кода, достаточно лишь переопределить его:  DEL_1 = new StringDelegate(cout_2);

3) Указание анонимного метода при инициализации DEL_2  позволяет задать способ обработки данных далее в программном коде (строка 3).  Переопределение экземпляра делегата, например:
DEL_2 = delegate (string s) { Console.WriteLine(«Вывод с заголовком\n» + s); };
также приведет к изменению результата действий оператора DEL_2(s);

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

5) С введением в язык C# 3.0  (2008 год)  лямбда-выражений они все чаще стали встречаться в программах для встраивания кода. Если вы не хотите видеть в описании класса большое количество именованных методов, то применяйте лямбда-выражения.

Немного теории

Анонимная функция (анонимный метод или лямбда-выражение) – это функция, которая не имеет имени а содержит только блок программного кода, который она выполняет. Анонимные функции удобно использовать в объединении с делегатами. Анонимную функцию можно вызвать только с помощью делегата. Сама функция непосредственно не вызовется никогда.

Анонимный метод

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

Объявление делегата похоже на объявление метода (кроме слова delegate):
delegate тип_возвр_параметра имя_делегата(список_параметров);
Инициализация экземпляра делегата:
имя_делегата имя_экземпляра = delegate(список_параметров):
Вызов делегата void:
имя_экземпляра(список_параметров);
или
объект=имя_экземпляра(список_параметров);
где объект имеет тот же тип, что и  тип возвращаемого параметра делегата.

Пример. Разместите на форме одну кнопку button1, но не связывайте ее с обработчиком события Click (в панели Свойства на вкладке События для элемента button1 никакие обработчики событий не должны быть заданы), а также элемент textBox1. Вставим следующий код в файл Form1.cs:

using System.Windows.Forms;
using System;
namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        delegate void Del(int x);
        Del d = delegate(int k) 
        {  System.Windows.Forms.MessageBox.Show(k.ToString() + " 
           в квадрате  = " + (k * k).ToString());
        };
        
        public Form1()
        {
            InitializeComponent();
            button1.Click += delegate(System.Object o, System.EventArgs e)
            { System.Windows.Forms.MessageBox.Show("Кнопка нажата!"); };
        }

        private void textBox1_TextChanged(object sender, System.EventArgs e)
        {
            int k = Convert.ToInt32(textBox1.Text);
            d(k);
        }  
    }
}

Обратите внимание на объявление в классе Form1 делегата void Del(int x). Следом за объявлением задается метод, который будет вызван по имени делегата с параметром. Этот делегат будет доступен в любом методе класса. Однако его можно и переопределить в нужном месте (своего рода перегрузка делегата).

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

При изменении числа в textBox1 на 5 открывается модальное окно с сообщением  «5 в квадрате = 25». При его закрытии мы переходим на главную форму. При нажатии на кнопку открывается модальное окно с сообщением «Кнопка нажата» , после закрытия которого мы снова возвращаемся на форму.

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

Лямбда-выражения

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

Чтобы создать лямбда-выражение, необходимо указать входные параметры (если они есть) с левой стороны лямбда-оператора =>, и поместить блок выражений или операторов с другой стороны.

Например, лямбда-выражение x => x * x задает параметр с именем x и возвращает значение x2.

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

delegate int DEL(int i);
static void Main(string[] args)
{
    DEL myDel = x => x * x;
    int j = myDel(7); // j = 49
}

Создание типа дерева выражений

using System.Linq.Expressions;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<del> myET = x => x * x;
        }
    }
}

>Оператор => имеет такой же приоритет, как и присваивание (=).Лямбда-операторы используются в запросах LINQ на основе методов в качестве аргументов стандартных методов операторов запроса.

Обратите внимание: в приведенном выше примере сигнатура делегата имеет один неявный входной параметр типа int и возвращает значение типа int. Лямбда-выражение можно преобразовать в делегат соответствующего типа, поскольку он также имеет один входной параметр (x) и возвращает значение, которое компилятор может неявно преобразовать в тип int.

Два вида лямбда-выражений: выражение-лямбда и лямбда оператора (терминологическое уточнение)

Лямбда-выражение с выражением с правой стороны оператора => называется выражением-лямбдой. Выражения-лямбды возвращают результат выражения и принимают следующую основную форму.
Если лямбда-выражение имеет только один входной параметр, скобки можно не ставить; во всех остальных случаях они обязательны. Два и более входных параметра разделяются запятыми и заключаются в скобки

(x, y) => x == y

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

(int x, string s) => s.Length > x

Нулевое количество входных параметры задается пустыми скобками

() => SomeMethod()

Обратите внимание, что тело выражения-лямбды может состоять из вызова метода, как было показано в предыдущем примере.

Лямбда оператора

Лямбда оператора напоминает выражение-лямбду, за исключением того, что оператор (или операторы) заключается в фигурные скобки

(input parameters) => {statement;}

Тело лямбды оператора может состоять из любого количества операторов; однако на практике обычно используется не более двух-трех.

delegate void TestDelegate(string s);
...
TestDelegate myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); };
myDel("Hello");

Лямбды операторов, как и анонимные методы, не могут использоваться для создания деревьев выражений.

Примечание об анонимных методах

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

Как вы видите, в анонимных методах нет ничего сложного. Пишите лямбда-выражения вместо методов, состоящих из одного-трех операторов.

 

1 комментарий к “Делегаты и анонимные функции”

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Пролистать наверх