代码之家  ›  专栏  ›  技术社区  ›  Lorenzo OnoSendai

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

  •  2
  • Lorenzo OnoSendai  · 技术社区  · 9 年前

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

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

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

    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应用于任何对象(我知道lambda应该使用Receipt类的ParameterExpression创建)
    2. 获取lambda的布尔结果(例如,名称是否等于Foo?)
    3. 对collections Count()方法应用相同的逻辑(例如,构建一个lambda来检查receive.Detail.Count()

    这可能吗?

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

    • 验证输入是否在一周中的某一天
    • 验证输入是否在特定时间范围内
    • 验证输入的字段“X”是否小于/等于/大于值
    • 验证输入的字段“Y”是否包含值
    • 验证作为集合的输入字段“Z”的计数是否小于/等于/大于值

    对于前4点,我已经能够动态创建lambda表达式,代码如下 P.Brian.Mackey answer ,我可以使用Specification模式将其应用于对象本身。

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

    3 回复  |  直到 7 年前
        1
  •  3
  •   P.Brian.Mackey    9 年前

    这里有一些东西可以让你开始。有很大的改进空间。尤其是丑陋的工厂。本演示旨在展示如何使用表达式解决问题,而不是作为最佳实践或工厂模式演示。如果有任何不清楚的地方,请随时要求澄清。

    用法

        [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 “实现迷你语言”。

        2
  •  1
  •   Shaun Luttin    9 年前

    你的问题很吸引人,我想了解你的要求。我创建了一个演示,我想知道,这个演示与您想要完成的有什么不同?这里有一个工作版本 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;
    }
    

    工具书类

        3
  •  0
  •   Pavel    9 年前

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

    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);
        }
    

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