将动态创建的lambda应用于对象实例

时间:2015-03-14 00:26:47

标签: c# lambda expression expression-trees

我有一些代码可以从字符串开始动态创建lambda。例如,我有一个类似以下的过滤器类:

public class Criteria {
    public string Property { get; set; }
    public string Operator { get; set; }
    public string Value { get; set; }
}

我可以从像这样的Criteria实例开始创建一个像x => x.Name == "Foo"这样的lambda

Criteria c = new Criteria() {
    Property = "Name",
    Operator = "equal",
    Value = "Foo"
}

假设有一个像

这样的课程
public class Receipt {
    public string Name { get; set; }
    public int Amount { get; set; }
    [other props omitted]
    public ICollection<ReceiptDetail> Details { get; set; }
}

我想:

  1. 将lambda应用于任何对象(我知道应该使用Receipt类的ParameterExpression创建lambda)
  2. 获取lambda的布尔结果(例如,名称是否等于Foo?)
  3. 将相同的逻辑应用于集合Count()方法(例如,构建一个检查收据的lambda.Details.Count()
  4. 这可能吗?

    编辑:根据评论,我正在详细阐述我的需求。这段代码让我有机会回答我的要求,并说:如果为我的对象指定了规则,那么应用程序的行为应该有所不同。虽然这是一个常见的要求,但我想创建一个代码,允许我扩展它,因为会添加更多规则。实际上我只有5种规则类型:

    • 验证输入是否来自一周中的特定日期
    • 验证输入是否来自特定时间范围
    • 验证字段&#34; X&#34;输入的值小于/等于/大于值
    • 验证字段&#34; Y&#34;输入包含值
    • 验证字段&#34; Z&#34;输入的集合,其集合的计数小于/等于/大于值

    对于前4个点,我能够动态创建一个lambda表达式,其代码类似于P.Brian.Mackey answer,我可以使用规范模式将其应用于对象本身。

    最后一点需要以相同的方式实现,但唯一的区别是表达式的左侧部分是方法调用而不是属性(特别是ICollection<T>.Count()方法)

3 个答案:

答案 0 :(得分:3)

这是让你入门的东西。还有很大的改进空间。特别是丑陋的工厂。此演示旨在说明如何使用表达式来解决问题,而不是作为最佳实践或工厂模式演示。如果有任何不清楚的地方,请随时要求澄清。

用法

    [Test]
    public void ApplySameLogicToCollectionsCount()
    {
        var receipt = new Receipt();
        var details = new ReceiptDetail();
        var details2 = new ReceiptDetail();
        receipt.Details.Add(details);
        receipt.Details.Add(details2);
        var result = LambdaGeneratorFactory<ICollection<ReceiptDetail>>.Run(detailsCount);
        Assert.IsTrue(result(receipt.Details));
    }

工厂

 public static class LambdaGeneratorFactory<T>
    {
        //This is an ugly implementation of a Factory pattern.
        //You should improve this possibly with interfaces, maybe abstract factory.  I'd start with an ICriteria.
        public static Predicate<T> Run(Criteria criteria)
        {
            if (typeof(T) == typeof (Receipt))
            {
                return CreateLambda(criteria);
            }
            else if (typeof (T) == typeof (ICollection<ReceiptDetail>))
            {
                return CreateLambdaWithCount(criteria);
            }

            return null;
        }
        private static Predicate<T> CreateLambda(Criteria criteria)
        {
            ParameterExpression pe = Expression.Parameter(typeof(T), "i");

            Expression left = Expression.Property(pe, typeof(T).GetProperty(criteria.Property));
            Expression right = Expression.Constant(criteria.Value);

            Expression predicateBody = Expression.Equal(left, right);

            var predicate = Expression.Lambda<Predicate<T>>(predicateBody, new ParameterExpression[] { pe }).Compile();

            return predicate;
        }

        private static Predicate<T> CreateLambdaWithCount(Criteria criteria)
        {
            ParameterExpression pe = Expression.Parameter(typeof(T), "i");

            Expression count = Expression.Property(pe, typeof(T).GetProperty("Count"));
            Expression left = Expression.Call(count, typeof(Object).GetMethod("ToString"));
            Expression right = Expression.Constant(criteria.Value);

            Expression predicateBody = Expression.Equal(left, right);

            var predicate = Expression.Lambda<Predicate<T>>(predicateBody, new ParameterExpression[] { pe }).Compile();

            return predicate;
        }
    }

标准

    private Criteria detailsCount = new Criteria()
    {
        Property = "Details",
        Operator = "equal",
        Value = "2"
    };

切换到ICriteria,事情会更清晰。一个更好的工厂,不需要ToString。编程到界面。

所有这一切,这段代码感觉有点时髦。从字符串生成函数有什么意义?我感觉这是从语法中产生C#的感觉。我不相信会扩展得很好。对于非平凡的实现,首先考虑lex/yacc。您可以在Pragmatic Programmer“实施迷你语言”中找到有关执行此操作的更多详细信息。

答案 1 :(得分:1)

你的问题是一个引人入胜的问题,我想了解这些要求。我创建了一个演示,我想知道,演示与你想要完成的演示有什么不同?这里有一个工作版本https://dotnetfiddle.net/AEBZ1w

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

public class Program
{
    public static void Main()
    {
        Criteria c = new Criteria() { 
            Property = "Name", 
            Operator = "==", 
            Value = "Foo" };

        var queryable = (new List<Receipt>() { 
            new Receipt { Name = "Foo", Amount = 1 },
            new Receipt { Name = "Foo", Amount = 2 }, 
            new Receipt { Name = "Bar" }  
        }).AsQueryable();

        var parameter = Expression.Parameter(typeof(Receipt), "x");
        var property = Expression.Property(parameter, typeof(Receipt).GetProperty(c.Property));
        var constant = Expression.Constant(c.Value);
        var operation = Expression.Equal(property, constant);
        var expression = Expression.Call(
            typeof(Queryable),
            "Where",
            new Type[] { queryable.ElementType },
            queryable.Expression, 
            Expression.Lambda<Func<Receipt, bool>>(operation, new ParameterExpression[] { parameter })
        );

        Console.WriteLine("Linq Expression: {0} \n", expression.ToString());
        Console.WriteLine("Results: \n");

        var results = queryable.Provider.CreateQuery<Receipt>(expression);
        foreach(var r in results)
        {
            Console.WriteLine("{0}:{1}", r.Name, r.Amount);
        }
    }
}

public class Criteria
{
    public string Property, Operator, Value;
}

public class ReceiptDetail
{
    public string ItemName;
}

public class Receipt
{
    public string Name { get; set; }
    public int Amount;
    public ICollection<ReceiptDetail> Details;
}

参考

答案 2 :(得分:0)

您可以使用泛型反射。解决问题的一种方法可能是扩展

public static class EnumerableExtensions
{
    public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Criteria c)
    {
        var sourceType = typeof(T);
        var propertyMember = sourceType.GetProperty(c.Property);
        Func<string, bool> predicate = null;
        switch (c.Operator)
        {
            case "equal":
                predicate = (v) => v == c.Value;
                break;
            // other operators
            default:
                throw new ArgumentException("Unsupported operator.");
        }
        return source.Where(v => predicate((string)propertyMember.GetMethod.Invoke(v, null)));
    }
}

您可以在代码中使用:

    void FooBar()
    {
        Criteria c = new Criteria()
        {
            Property = "Name",
            Operator = "equal",
            Value = "foo"
        };

        var source = new Receipt[2];
        source[0] = new Receipt { Name = "foo", Amount = 1 };
        source[1] = new Receipt { Name = "bar", Amount = 2 };

        var result = source.Where(c);
    }

这只是为了给你一个想法。改进将是错误处理(未找到属性,无效转换,空值等),重构以启用单元测试(例如,注入选择“策略”)和性能(例如,构建,编译和缓存表达式树而不是反射)。这应该为您提供足够的关键词来了解。希望这会有所帮助。