Почему различаются ParameterExpression?

173
03 октября 2018, 18:50

Разбираюсь с динамическим построением лямбда-запросов.

Вот мой исходный пример, неработающий:

void Main()
{
    var data = Sample();
    var filter = new Filter { Text = "ov", CityId = 1, };
    var result = ExecuteQuery(data.AsQueryable(), filter);
    result.Dump();
}
// Define other methods and classes here
public class Contact
{
    public int Id { get; set; }
    public string Title { get; set; }
    public int? CityId { get; set; }
}
public class Filter
{
    public string Text { get; set; }
    public int? CityId { get; set; }
}
public List<Contact> Sample()
{
    return new List<Contact>
    {
        new Contact { Id = 1, Title = "Ivanov", CityId = null, },
        new Contact { Id = 2, Title = "Petrov", CityId = null, },
        new Contact { Id = 3, Title = "Sidorov", CityId = 1, },
        new Contact { Id = 4, Title = "Twain", CityId = 2, },
        new Contact { Id = 5, Title = "Smith", CityId = 2, },
    };
}
public IEnumerable<Contact> ExecuteQuery(IQueryable<Contact> data, Filter filter)
{
    Expression<Func<Contact, bool>> expr = x => true;
    if(!string.IsNullOrWhiteSpace(filter.Text))
    {
        Expression<Func<Contact, bool>> exprText = x => x.Title.Contains(filter.Text);
        var body = Expression.AndAlso(expr.Body, exprText.Body);
        expr = Expression.Lambda<Func<Contact, bool>>(body, expr.Parameters[0]);
    }
    if(filter.CityId != null)
    {
        Expression<Func<Contact, bool>> exprCity = x => x.CityId == filter.CityId;
        var body = Expression.AndAlso(expr.Body, exprCity.Body);
        expr = Expression.Lambda<Func<Contact, bool>>(body, expr.Parameters[0]);
    }
    return data.Where(expr);
}

Когда я формировал этот код (на основании этого ответа) я почему-то думал, что параметры у меня всегда совпадают.

Я в отладчике смотрел первые две лямбды:

ParameterExpression param1 = expr.Parameters[0];
ParameterExpression param2 = exprText.Parameters[0];
if (ReferenceEquals(param1, param2))
{

Но так и не понял, почему они различаются:

Кто-нибудь может пояснить этот момент?

PS Как переделать код к рабочему виду я уже сам разобрался:

public IEnumerable<Contact> ExecuteQuery(IQueryable<Contact> data, Filter filter)
{
    Expression<Func<Contact, bool>> expr = x => true;
    if(!string.IsNullOrWhiteSpace(filter.Text))
    {
        Expression<Func<Contact, bool>> exprText = x => x.Title.Contains(filter.Text);
        expr = expr.AndAlso<Contact>(exprText);
    }
    if(filter.CityId != null)
    {
        Expression<Func<Contact, bool>> exprCity = x => x.CityId == filter.CityId;
        expr = expr.AndAlso<Contact>(exprCity);
    }
    return data.Where(expr);
}
public static class MyExt
{
    public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
    {
        ParameterExpression param = expr1.Parameters[0];
        if (ReferenceEquals(param, expr2.Parameters[0]))
        {
            return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, expr2.Body), param);
        }
        return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, Expression.Invoke(expr2, param)), param);
    }
}
Answer 1

Они различаются по той простой причине что все Expression всегда сравниваются по ссылке. Каждый вызов Expression.Parameter создает уникальный параметр.

Эти параметры могут оказаться равными только в том случае когда кто-то скопирует их, явно или в составе выражения. В вашем случае это возможно, например, при вызове MyExt.AndAlso(exprText, exprText).

Если же вы спросите "зачем так сделано" - то вот простой ответ: то, как вы пытаетесь объединить два выражения - не единственный способ это сделать. И другим конструкциям нужны именно уникальные параметры:

Expression<Func<Foo, int>> foo = x => x.Y; 
Expression<Func<Bar, int>> bar = x => x.Z;
var expr = Expression.Lambda<Func<Foo, Bar, int>>(
    Expression.Add(foo.Body, bar.Body),
    foo.Parameters[0],
    bar.Parameters[0]);
READ ALSO
Действия в программе в свернутом режиме

Действия в программе в свернутом режиме

У меня появилась такая проблема:

161
Asset Bundles Unity не загружаются в билде под UWP

Asset Bundles Unity не загружаются в билде под UWP

Работаю над приложением для Mixed RealityЗадача загружать сцены из интернета и загружать их соответственно

169
XAML. Рамка кнопок

XAML. Рамка кнопок

Есть такой код на XAML

252
Сохранить изображение большого грфика winforms

Сохранить изображение большого грфика winforms

Использую winForms для построения графиковМетод для сохранения изображения :

151