• Microsoft .NET
  • C#.NET
  • Красота фракталов - написание простой программы визуализации фракталов на языке C#

Изучение лямбда-выражений в C# - Создание выражения из другого выражения

ОГЛАВЛЕНИЕ

Создание выражения из другого выражения

Можно взять дерево выражения и изменить его, чтобы создать другое выражение из него. В следующем примере начинаем с лямбда-выражения x *x, затем меняем это выражение, прибавляя к нему 2. Рассмотрим пример:

public static void CreatingAnExpressionFromAnotherExpression()
    {
        Expression<Func<int, int>> square = x => x * x;
        BinaryExpression squareplus2 = Expression.Add(square.Body,
            Expression.Constant(2));
        Expression<Func<int, int>> expr = Expression.Lambda<Func<int, int>>(squareplus2,
            square.Parameters);

        Func<int, int> compile = expr.Compile();
        Console.WriteLine(compile(10));
    }

Начинаем с лямбда-выражения, возвращающего square(квадрат):

Expression<Func<int, int>> square = x => x * x;

Затем генерируем тело нового лямбда-выражения, используя тело первого лямбда-выражения и прибавляя константу 2 к нему и назначая ее двоичному выражению:

BinaryExpression squareplus2 = Expression.Add(square.Body, Expression.Constant(2));

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

Expression<Func<int, int>> expr = Expression.Lambda<Func<int, int>>(squareplus2,
    square.Parameters);

Замыкания и лямбда-выражения

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

public static void LambdaWithClosure()
    {
        int mulitplyby = 2;
        Func<int, int> operation = x => x * mulitplyby;
        Console.WriteLine(operation(2));
    }

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

Есть определенные ограничения при использовании лямбда-выражений с параметром с ключевым словом ref и out. Если переменная передается с ключевым словом ref или out, надо явно задать тип параметра, потому что компилятор не может вывести тип переменной. Как показано в примере ниже:

delegate void OutParameter(out int i);
    delegate void RefParameter(ref int i);
    public static void GotchasWithLambdas()
    {
        //пример с параметром out int i;
        OutParameter something = (out int x) => x = 5;
        something(out i);
        Console.WriteLine(i);

        //пример с параметром ref.
        int a = 2;
        RefParameter test = (ref int x) => x++;
        test(ref a);
        Console.WriteLine(a);
    }

Обратите внимание, что в коде выше явно задан тип параметра int в обоих случаях, ref и out. Если опустить тип параметра, компилятор выдаст ошибку.

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

delegate void ParmsParameter(params int[] ints);
    public static void LambdaWithParam()
    {
        ParmsParameter par = (params int[] ints) =>
        {
            foreach (int i in ints)
            {
                Console.WriteLine(i);
            }
        };
    }

Вывод

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