понедельник, 27 июня 2011 г.

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

Несколько слов вместо введения.
В данной статье я хочу рассказать о лямбда-выражениях - что это такое и с чем их едят. Частенько на форумах можно встретить вопросы вроде "зачем они нужны?", "нечитабельны" и т.д. Думаю многих отталкивает синтаксис лямбда-выражений, который может показаться простым и сложным одновременно. Главное усвоить, что лямбда-выражения предназначены для определения анонимных методов в сжатой манере, тем самым еще больше упрощая работу с делегатами.
Т.к. эта статья не нацелена на обзор того, что из себя представляют делегаты и анонимные методы, то предлагаю быстро пробежаться от делегатов через анонимные методы до лямбда-выражений на простом примере.

Делегаты, анонимные методы и лямбда-выражения
В качестве примера рассмотрим простой код, где нам надо получить сумму или разницу 2 целых чисел стандартным образом:
using System;
namespace SimpleLambda
{
  class Program
  {
    static void Main(string[] args)
    {
      //Стандартный способ
      Console.WriteLine("5 + 3 = {0}", Sum(5, 3));
      Console.WriteLine("5 - 3 = {0}", Sub(5, 3));

      Console.ReadLine();
    }

    //Сумма двух чисел
    public static int Sum(int x, int y)
    {
      return x + y;
    }

    //Разница двух чисел
    public static int Sub(int x, int y)
    {
      return x - y;
    }
  }
}
Результат вывода в консоль:
5 + 3 = 8
5 - 3 = 2


Но вот незадача. Иногда может потребоваться, чтобы один метод вызывал другой, проще говоря передать ему указатель на функцию. Для решения этой проблемы существуют делегаты:
using System;
namespace SimpleLambda
{
  class Program
  {
    public delegate int MathDelegate(int x, int y); //Объявим делегат, принимающий два аргумента типа int, и возвращающий int

    static void Main(string[] args)
    {
      //Стандартный способ
      Console.WriteLine("5 + 3 = {0}", Sum(5, 3));
      Console.WriteLine("5 - 3 = {0}", Sub(5, 3));

      Console.WriteLine();

      //Использование делегата
      MathDelegate md = new MathDelegate(Sum); //Передаем делегату ссылку на метод Sum
      Console.WriteLine("5 + 3 = {0}", md(5, 3)); 
      md = Sub; //Теперь передаем делегату ссылку на метод Sub
      Console.WriteLine("5 - 3 = {0}", md(5, 3));

      Console.ReadLine();
    }

    //Sums to numbers
    public static int Sum(int x, int y)
    {
      return x + y;
    }

    //Substracts two numbers
      public static int Sub(int x, int y)
    {
      return x - y;
    }
  }
}
Результат вывода на экран:
5 + 3 = 8
5 - 3 = 2

5 + 3 = 8
5 - 3 = 2


Теперь все работает как часы. Есть надобность сложить два числа - делегат с этим справится, надо получить разницу двух чисел - делегат тут как тут. Но что, если нам нужно провести комплексную операцию над двумя числами? К примеру, получить сумму двух чисел во второй степени. Создавать отдельный метод с подобной функциональностью может быть затратным по времени, особенно когда такую операцию нужно сделать всего один раз.
Начиная с C# 2.0 появилась возможность использовать анонимные методы, позволяющие передавать делегату блок кода и избежать при этом создания отдельного метода.
Итак, дополним наш пример используя анонимный метод для вычисления суммы двух чисел во второй степени:
using System;
namespace SimpleLambda
{
  class Program
  {
    public delegate int MathDelegate(int x, int y); //Объявим делегат, принимающий два аргумента типа int, и возвращающий int

    static void Main(string[] args)
    {
      //Стандартный способ
      Console.WriteLine("5 + 3 = {0}", Sum(5, 3));
      Console.WriteLine("5 - 3 = {0}", Sub(5, 3));

      Console.WriteLine();

      //Использование делегата
      MathDelegate md = new MathDelegate(Sum); //Передаем делегату ссылку на метод Sum
      Console.WriteLine("5 + 3 = {0}", md(5, 3)); 
      md = Sub; //Теперь передаем делегату ссылку на метод Sub
      Console.WriteLine("5 - 3 = {0}", md(5, 3));

      Console.WriteLine();

      //Использование анонимного метода
      md = delegate (int x, int y) { return ((x*x) + (y*y)); }; //Передаем делегату блок кода, который и является анонимным методом
      Console.WriteLine("5*5 + 3*3 = {0}", md(5, 3));

      Console.ReadLine();
    }

    //Sums to numbers
    public static int Sum(int x, int y)
    {
      return x + y;
    }

    //Substracts two numbers
    public static int Sub(int x, int y)
    {
      return x - y;
    }
  }
}
Результат вывода на экран:
5 + 3 = 8
5 - 3 = 2

5 + 3 = 8
5 - 3 = 2

5*5 + 3*3 = 34


Начиная с C# 3.0 появились лямбда-выражения, которые заменяют анонимные методы. Добавим в нашем примере лямбда-выражение, функционально идентичное анонимному методу:
using System;
namespace SimpleLambda
{
  class Program
  {
    public delegate int MathDelegate(int x, int y); //Объявим делегат, принимающий два аргумента типа int, и возвращающий int

    static void Main(string[] args)
    {
      //Стандартный способ
      Console.WriteLine("5 + 3 = {0}", Sum(5, 3));
      Console.WriteLine("5 - 3 = {0}", Sub(5, 3));

      Console.WriteLine();

      //Использование делегата
      MathDelegate md = new MathDelegate(Sum); //Передаем делегату ссылку на метод Sum
      Console.WriteLine("5 + 3 = {0}", md(5, 3)); 
      md = Sub; //Теперь передаем делегату ссылку на метод Sub
      Console.WriteLine("5 - 3 = {0}", md(5, 3));

      Console.WriteLine();

      //Использование анонимного метода
      md = delegate (int x, int y) { return ((x*x) + (y*y)); }; //Передаем делегату блок кода, который и является анонимным методом
      Console.WriteLine("5*5 + 3*3 = {0}", md(5, 3));

      Console.WriteLine();

      //Использование лямбда-выражения, вместо анонимного метода
      md = (x, y) => ((x*x) + (y*y)); //Лямбда-выражение собственной персоной
      Console.WriteLine("5*5 + 3*3 = {0}", md(5, 3));

      Console.ReadLine();
    }

    //Sums to numbers
    public static int Sum(int x, int y)
    {
      return x + y;
    }

    //Substracts two numbers
    public static int Sub(int x, int y)
    {
      return x - y;
    }
  }
}
Результат вывода на экран:
5 + 3 = 8
5 - 3 = 2

5 + 3 = 8
5 - 3 = 2

5*5 + 3*3 = 34

5*5 + 3*3 = 34


Немного о лямбда-выражениях.
Лямбда-выражения могут испугать, но сложного в них ничего нету. Вы это поймете буквально через несколько минут.
Читаются лямбда-выражения очень просто. Сначала указывается список аргументов, затем следует лямбда-оператор (=>), а после следует блок кода, который должен выполниться. Схематично это выглядит так:
список_аргументов => блок_кода
Например:
//(x, y) - список_аргументов
//((x*x) + (y*y)) - блок_кода
md = (x, y) => ((x*x) + (y*y));
Если аргументы отсутствуют, то в качестве параметров указывается пара пустых скобок:
public delegate void NewDelegate();
NewDelegate del = () => Console.WriteLine(DateTime.Now.ToString());
del(); //Выведет в консоль нынешнее дату и время
Для обработки нескольких строк, блок кода в лямбда-выражении необходимо выделить с помощью фигурных скобок:
del = () => {
Console.WriteLine("----- Some additional code -----");
Console.WriteLine(DateTime.Now.ToString());
};
Типы аргументов можно указывать, можно не указывать. Они определяются компилятором автоматически на основе сигнатуры делегата, тоже самое касается и типа возвращаемого значения.

На этом все. И надеюсь трудности, которые вызывали лямбда-выражения, теперь остались позади.

11 комментариев:

  1. Отлично! Все понятно, спасибо.

    ОтветитьУдалить
  2. спасибо, вот это по-человечески. на msdn так мудрёно написано, что по полчаса сидишь и вникаешь в каждое слово.

    ОтветитьУдалить
  3. Очень интересно было почитать! Спасибо!

    ОтветитьУдалить
  4. Боже, да это самое лучшее объяснение, что я встречал, три дня не мог въехать в эти делегаты, а тут все за пару минут стало понятно, огромное человеческое спасибо!!!

    ОтветитьУдалить
  5. Статья шикарная! Очень доступное обьяснение. Огромное спасибо автору)

    ОтветитьУдалить
  6. Присоединяюсь ко всем комментаторам выше! Шикарное объяснение)

    ОтветитьУдалить
  7. Грамотный подход к изложению материала. Все как обычно - дело в учителях.
    Напутствие- не сбиваться и идти в лаконичном направлении, успеха!

    ОтветитьУдалить
  8. Спасибо за изложение лямбда :)

    ОтветитьУдалить
  9. Браво, отличный материал!! С приветом из Грузии.

    ОтветитьУдалить
  10. Несколько раз я читал про них в разных местах. И вот наконец я вижу объяснение оставляющее в голове структуру которую можно вызвать у себя в памяти! Отдельно отмечу представление развития языка от делегатов через анонимные методы и собственно к лямбда. Спасибо!

    ОтветитьУдалить
  11. Респектище!!! Перечитал кучу статей... и вот на тебе! Везде тонны написано и непонятно, а человек взял и объяснил. Четко и последовательно. Действительно объяснить просто дорогого стоит. Низкий поклон и уважуха!

    ОтветитьУдалить