c => c.FirstName
c => c.Email
c => c.Photo
Конструкции вида x => x.Email
(а также более сложные, вида (x, y) => x.Email + y.Age
или там n => { int s = 0; while (n-- > 0) s += n * n; return s; }
) — это лямбда-выражения. Они используются в C# в двух смыслах.
Если в этом месте ожидается делегат/метод/что-то такое, то лямбда имеет смысл локально-определённой функции. Например, такой код:
Func<Entity, string> f = (c => c.FirstName);
по существу не отличается от кода
Func<Entity, string> f = GetFirstName;
static string GetFirstName(Entity c)
{
return c.FirstName;
}
(но записывается быстрее). Кроме того (и это очень важно и удобно), лямбды могут ссылаться на локальные переменные и поля, видимые в точке определения. Например, так:
int x = hey.GimmeSomeX();
Func<int, int> f = n => n * x;
Такого эффекта уже нельзя добиться с локальной функцией, поэтому компилятор «под капотом» использует более сложную конструкцию.
Если же в месте, где упоминается лямбда, ожидается специальная штука под названием дерево выражений, то лямбда конвертируется в это самое дерево выражений. Имея дерево выражений, можно посмотреть программным путём, что же там внутри. В частности, если у вас есть c.FirstName
, вы можете увидеть, что это обращение к полю с именем FirstName
. Именно для этого оно используется в вашем примере.
Чем же дерево выражений лучше, чем просто передать строку "FirstName"
? Дело в том, что в строке вы можете ошибиться, а вот за правильностью лямбда-выражения следит компилятор. Также при переименовании IDE сможет правильно подхватить изменения. А со строкой возможны, понятно, проблемы.
Подобный код используется в C# для того, чтобы указать имя свойства без риска опечататься. В классическом коде это могло бы выглядеть так:
modelBuilder.Entity<Customer>()
.Property("FirstName")
.IsRequired()
.HasMaxLength(30);
Понятно, что при переименовании свойства FirstName
код продолжает компилироваться, но перестаёт работать во время выполнения. Было бы неплохо использовать такую конструкцию, которая при переименовании свойства приводила бы к ошибке компиляции. Это давало бы нам возможность сразу обнаруживать и исправлять опечатки в названии полей.
Как раз для этого и применяют синтаксис, основанный на деревьях выражений:
public PrimitivePropertyConfiguration Property<T>(
Expression<Func<TStructuralType, T>> propertyExpression
)
where T : struct, new()
{
. . .
}
Синтаксис страшноват, но он позволяет статически типизировать обращение к любому свойству класса. Что в данном случае происходит?
Func<TStructuralType, T>
означает, что на вход ожидается лямбда-выражение, оно же анонимная (безымянная) функция. Синтаксис таких функций в C# выглядит как x => Exp(x)
, где x
— это параметр функции, а Exp(x)
— какое-то не очень сложное выражение. В нашем случае это простое выражение — обращение к свойству объекта.
Expression<Func<TStructuralType, T>>
означает, что мы не будем выполнять функцию непосредственно, а вместо этого построим дерево выражения и сохраним его в переменной типа Expression
.
Объект этого класса будет доступен во время выполнения и мы сможем пройтись по нему, и извлечь название свойства.
То, что это Expression
именно от Func<TStructuralType, T>
ограничивает способ задания параметра propertyExpression
: мы ждём либо имя функции с одним параметром, либо анонимную функцию с одним параметром (она же лямбда).
Проще говоря, Expression<Func<TStructuralType, T>>
означает, что при вызове Property
в качестве параметра ожидается что-то вроде x => Exp(x)
. Вы всё ещё можете выстрелить себе в ногу и написать неподходящее выражение, например, x => x + x
, но так делать всё-таки не стоит.
Наконец, в чём главная магия, как устроен метод Property
? В основе, конечно, лежит рефлексия и подробности можно посмотреть в ответе на соответствующий вопрос на StackOverflow (англ.).
Непосредственно в Entity Framework методы, извлекающие имя свойства из выражения, вынесены в класс ExpressionExtensions
.
Виртуальный выделенный сервер (VDS) становится отличным выбором
Добрый день! Видел много вопросов по этому поводу но ответа так и не нашёл
Для того, чтобы отобразить свойства и поля моего класса в PropertyGrid, я для них задаю атрибуты PropertyGridНапример :