首页 > 解决方案 > 如何在表达式中使用委托

问题描述

我正在努力理解如何构造一个使用委托的表达式。我是表达式的新手,令人尴尬的是无法创建一个单元测试来复制我看到的问题,所以希望下面的信息足以解释这个问题。

考虑以下类:

public class Instance
{
    internal Instance(string value)
    {
        Value = value;
    }

    public string Value { get; }
    public Expression<Func<Container, bool>> IsContainerMatch => c => Selector(c.Item) == Value;
    public Func<Item, string> Selector => i => i.Value;

}

public class Container
{
    internal Container(Item item)
    {
        Item = item;
    }
    public Item Item { get; }
}

public class Item
{
    internal Item(string value)
    {
        Value = value;
    }
    public string Value { get; }
}

IsContainerMatch表达式被用作Instance基于第三方方法的参数,并在以后编译/使用。但是,当实际调用表达式时,我收到一个错误,指出the variablec is referenced from scope '' but is not defined。如果我没记错的话,如果我可以将Selectordelegate合并到表达式中,就可以解决这个问题,使两者具有相同的范围。(如果我错了,请纠正我!)

我遇到了这个问题,但我看到的一个根本区别是我的代表的论点不是常数。它是在编译时确定的。我没有太多运气弄清楚如何为我的场景构建表达式。有人可以给我一点指导吗?

编辑:这是我可以构建的最简单的测试失败 - 抱歉,它不容易重复。仅当我尝试将表达式与 NHibernate 结合使用时才会出现此问题;当我使用@juharr 中的方法时,调用函数可以正常工作。当我将表达式注入 .And 语句时,我收到错误消息variable c of type Container referenced from scope '', but it is not defined

        [Fact]
        public void Test()
        {
            var instanceList = new Collection<Instance>{new Instance("test")};
            var dbConfig = OracleDataClientConfiguration.Oracle10
                .ConnectionString(c => c.Is("Data Source=(DESCRIPTION =(ADDRESS = (PROTOCOL = TCP)(HOST = 10.11.12.13)(PORT = 1521))(CONNECT_DATA = (SERVER = DEDICATED)(SERVICE_NAME = orcl.test.com)));Password=test;User ID=test;"))
                .Driver<NHibernate.Driver.OracleManagedDataClientDriver>();

            FluentConfiguration configuration = Fluently.Configure().Database(dbConfig)
                .Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly()));

            var sessionFactory = configuration.BuildSessionFactory();
            var session = sessionFactory.OpenSession();
            var query = session.QueryOver<Container>();
            foreach (var element in instanceList) query.And(c => element.Selector(c.Item) == element.Value);

            var query2 = session.QueryOver<Container>();
            foreach (var element in instanceList) query.And(element.IsContainerMatch);
        }

编辑#2:注意上面的query2;这是我的第二个用例。第一个查询是引发异常的查询,但我的意图是在第一个查询和第二个查询的表达式中重用委托。

标签: c#.netdelegatesexpression-trees

解决方案


好的,让我们打开它;

public Expression<Func<Container, bool>> IsContainerMatch => c => Selector(c.Item) == Value;

IsContainerMatch 是一个属性,在每次获取时返回一个新的表达式实例。该表达式包含对实例的常量引用以访问值和选择器。这大致相当于;

public Expression<Func<Container, bool>> IsContainerMatch { get {
    var inst = Expression.Constant(this);
    var container = Expression.Parameter(typeof(Container), "c");
    var selector = typeof(Instance).GetProperty("Selector");
    var value = typeof(Instance).GetProperty("Value");
    var item = typeof(Container).GetProperty("Item");
    return Expression.Lambda<Func<Container, bool>>(
        Expression.Equal(
            Expression.Invoke(
                Expression.MakeMemberAccess(inst, selector),
                Expression.MakeMemberAccess(container, item)
                ),
            Expression.MakeMemberAccess(inst, value)
            ),
        container
    );
} }

这不太可能是您的异常的真正来源。在其他地方构建了一个新的 LambdaExpression,可能是从这个 Expression 的片段中构建的,并引用了这个 ParameterExpression 'C'。但使用不同的参数。

例如,这样的事情可能会导致该异常;

    ...
    return Expression.Lambda<Func<Container, bool>>(
        Expression.Equal(
            Expression.Invoke(
                Expression.MakeMemberAccess(inst, selector),
                Expression.MakeMemberAccess(Expression.Parameter(typeof(Container), "X"), item)
                ),
            Expression.MakeMemberAccess(inst, value)
            ),
        Expression.Parameter(typeof(Container), "Y")
    );

显然,您正在尝试使用您的 3rd 方库不支持的表达式类型。

现在您已经更新了包含 NHibernate 的问题,看起来您想要实现的是;

foreach (var element in instanceList) query.And(c => c.Item.[Dynamic member] == element.Value);

以便 NHibernate 可以有效地评估标准。但是由于您的 Selector 是一个编译后的 Func,NHibernate 无法查看该方法的内部并将其转换为有效的查询。


推荐阅读