Страницы

Поиск по вопросам

среда, 18 декабря 2019 г.

Что за инструкция с оператором => в коде?

#c_sharp #lambda


// настройка полей с помощью Fluent API
modelBuilder.Entity()
    .Property(c => c.FirstName).IsRequired().HasMaxLength(30);

modelBuilder.Entity()
    .Property(c => c.Email).HasMaxLength(100);

modelBuilder.Entity()
    .Property(c => c.Photo).HasColumnType("image");


Что это вот за инструкции в коде выше?

c => c.FirstName
c => c.Email
c => c.Photo

    


Ответы

Ответ 1



Конструкции вида x => x.Email (а также более сложные, вида (x, y) => x.Email + y.Age или там n => { int s = 0; while (n-- > 0) s += n * n; return s; }) — это лямбда-выражения. Они используются в C# в двух смыслах. Если в этом месте ожидается делегат/метод/что-то такое, то лямбда имеет смысл локально-определённой функции. Например, такой код: Func f = (c => c.FirstName); по существу не отличается от кода Func f = GetFirstName; static string GetFirstName(Entity c) { return c.FirstName; } (но записывается быстрее). Кроме того (и это очень важно и удобно), лямбды могут ссылаться на локальные переменные и поля, видимые в точке определения. Например, так: int x = hey.GimmeSomeX(); Func f = n => n * x; Такого эффекта уже нельзя добиться с локальной функцией, поэтому компилятор «под капотом» использует более сложную конструкцию. Если же в месте, где упоминается лямбда, ожидается специальная штука под названием дерево выражений, то лямбда конвертируется в это самое дерево выражений. Имея дерево выражений, можно посмотреть программным путём, что же там внутри. В частности, если у вас есть c.FirstName, вы можете увидеть, что это обращение к полю с именем FirstName. Именно для этого оно используется в вашем примере. Чем же дерево выражений лучше, чем просто передать строку "FirstName"? Дело в том, что в строке вы можете ошибиться, а вот за правильностью лямбда-выражения следит компилятор. Также при переименовании IDE сможет правильно подхватить изменения. А со строкой возможны, понятно, проблемы.

Ответ 2



Подобный код используется в C# для того, чтобы указать имя свойства без риска опечататься. В классическом коде это могло бы выглядеть так: modelBuilder.Entity() .Property("FirstName") .IsRequired() .HasMaxLength(30); Понятно, что при переименовании свойства FirstName код продолжает компилироваться, но перестаёт работать во время выполнения. Было бы неплохо использовать такую конструкцию, которая при переименовании свойства приводила бы к ошибке компиляции. Это давало бы нам возможность сразу обнаруживать и исправлять опечатки в названии полей. Как раз для этого и применяют синтаксис, основанный на деревьях выражений: public PrimitivePropertyConfiguration Property( Expression> propertyExpression ) where T : struct, new() { . . . } Синтаксис страшноват, но он позволяет статически типизировать обращение к любому свойству класса. Что в данном случае происходит? Func означает, что на вход ожидается лямбда-выражение, оно же анонимная (безымянная) функция. Синтаксис таких функций в C# выглядит как x => Exp(x), где x — это параметр функции, а Exp(x) — какое-то не очень сложное выражение. В нашем случае это простое выражение — обращение к свойству объекта. Expression> означает, что мы не будем выполнять функцию непосредственно, а вместо этого построим дерево выражения и сохраним его в переменной типа Expression. Объект этого класса будет доступен во время выполнения и мы сможем пройтись по нему, и извлечь название свойства. То, что это Expression именно от Func ограничивает способ задания параметра propertyExpression: мы ждём либо имя функции с одним параметром, либо анонимную функцию с одним параметром (она же лямбда). Проще говоря, Expression> означает, что при вызове Property в качестве параметра ожидается что-то вроде x => Exp(x). Вы всё ещё можете выстрелить себе в ногу и написать неподходящее выражение, например, x => x + x, но так делать всё-таки не стоит. Наконец, в чём главная магия, как устроен метод Property? В основе, конечно, лежит рефлексия и подробности можно посмотреть в ответе на соответствующий вопрос на StackOverflow (англ.). Непосредственно в Entity Framework методы, извлекающие имя свойства из выражения, вынесены в класс ExpressionExtensions.

Комментариев нет:

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