【问题标题】:How to convert Linq expression to Reflection (To make dynamic LINQ expression)如何将 Linq 表达式转换为反射(制作动态 LINQ 表达式)
【发布时间】:2017-03-01 11:59:39
【问题描述】:

我创建了三个类。 两个类DataIntArrayEqualityComparer 在下面-

public class Data
    {   
        public Dictionary<int[], List<double>> s = new Dictionary<int[], List<double>>(new IntArrayEqualityComparer());        

        public Data()
        {
        }

        public Data(Dictionary<int[], List<double>> s)
        {
            this.s = s;
        }

        public Dictionary<int[], List<double>> S
        {
            get { return s; }
            set { s = value; }
        }
    }

 public class IntArrayEqualityComparer : IEqualityComparer<int[]>
    {
        public bool Equals(int[] x, int[] y)
        {
            if (x.Length != y.Length)
            {
                return false;
            }
            for (int i = 0; i < x.Length; i++)
            {
                if (x[i] != y[i])
                {
                    return false;
                }
            }
            return true;
        }

        public int GetHashCode(int[] obj)
        {
            int result = 17;
            for (int i = 0; i < obj.Length; i++)
            {
                unchecked
                {
                    result = result * 23 + obj[i];
                }
            }
            return result;
        }
    }

创建了名为Expression 的第三个类,我需要在其中将 LINQ 表达式转换为反射 -

public class Expresion
    {
        public void CreateExpression()
        {
            Expression<Func<Data, List<int>>> exp1 = null;
            //Below is the LINQ expression I want to convert
            exp1 = p2 => p2.s[new int[] { 14, 5 }].Select((item, index) => new { item, index }).Select(x => x.index).ToList();

            ParameterExpression p1 = Expression.Parameter(typeof(Data), "p");
            MethodInfo mInfo = typeof(List<double>).GetMethod("get_Item");
            MethodInfo mInfo1 = typeof(Dictionary<int, List<double>>).GetMethod("get_Item");
            MethodInfo mInfo2 = typeof(Dictionary<int[], List<double>>).GetMethod("get_Item");
            MethodInfo mInfo3 = typeof(List<int[]>).GetMethod("get_Item");

            MemberExpression s1 = Expression.Property(p1, "s");
            ParameterExpression index1 = Expression.Parameter(typeof(int), "index");
            ParameterExpression item1 = Expression.Parameter(typeof(double), "item");

            //Here I want to covert the "(item, index) => new { item, index }" part from LINQ expression into Reflection
        }
    }

【问题讨论】:

  • 我不会依赖"get_Item" 作为MethodInfo 对象,您可能应该通过查找GetIndexParameters 具有计数的索引器来找到索引器PropertyInfo。但这只是我。要执行您想要的操作,您必须使用适当的重载从 Enumerable.Select 创建泛型方法调用,创建一个 lambda 表达式(通知您的匿名类型)并将其传递给它。问题将是构建您的匿名类型,可以在此处看到一个示例:stackoverflow.com/a/3740637/491907
  • 顺便说一句,专业提示:编写Expression&lt;Func&lt;... &gt;&gt; exp = p2 =&gt;.... 并在其后设置断点。然后使用调试器检查expDebugView“属性”,看看它是如何构造的。在这种情况下它可能不起作用,但它肯定会有所帮助。您还可以安装其他更易于阅读的表达式可视化。
  • 你可能把事情复杂化了。如果您所做的只是选择索引,您实际上并不需要匿名类型。您可以删除第二个链接的Select,然后将索引作为 int 返回第一个。
  • 从技术上讲,您不能完全在运行时创建此表达式...您需要在某处使用匿名类型 new { item, index },以便编译器创建它。

标签: c# linq expression-trees


【解决方案1】:

可能是我手工构建的最复杂、最无用的Expression 树。内嵌评论。

public class Expresion {
    // We need the anonymous type that we want to use
    private static readonly Type AnonymousType = new { item = 0.0, index = 0 }.GetType();

    public void CreateExpression() {
        //Below is the LINQ expression I want to convert
        Expression<Func<Data, List<int>>> exp2 = p => p.s[new int[] { 14, 5 }].Select((item, index) => new { item, index }).Select(x => x.index).ToList();

        ParameterExpression p1 = Expression.Parameter(typeof(Data), "p");

        MemberExpression s1 = Expression.PropertyOrField(p1, "s");

        // The indexer
        PropertyInfo dictItem = s1.Type.GetProperty("Item");

        // The key to the dictionary, new int[] { 14, 5 }
        var key = Expression.NewArrayInit(typeof(int), Expression.Constant(14), Expression.Constant(5));

        // s[new int[] { 14, 5 }]
        var getItem = Expression.Property(s1, dictItem, key);

        // Enumerable.Select with indexer (generic)
        var enumerableSelectIndexTSourceTResult = (from x in typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
                                                   where x.Name == "Select" && x.IsGenericMethod
                                                   let args = x.GetGenericArguments()
                                                   where args.Length == 2
                                                   let pars = x.GetParameters()
                                                   where pars.Length == 2 &&
                                                       pars[0].ParameterType == typeof(IEnumerable<>).MakeGenericType(args[0]) &&
                                                       pars[1].ParameterType == typeof(Func<,,>).MakeGenericType(args[0], typeof(int), args[1])
                                                   select x).Single();

        // Enumerable.Select with indexer (non-generic)
        var enumerableSelectIndex = enumerableSelectIndexTSourceTResult.MakeGenericMethod(typeof(double), AnonymousType);

        // Inner function start
        ParameterExpression item1 = Expression.Parameter(typeof(double), "item");
        ParameterExpression index1 = Expression.Parameter(typeof(int), "index");

        var innerExpression1 = Expression.Lambda(Expression.New(AnonymousType.GetConstructors().Single(), item1, index1), item1, index1);

        // .Select((item, index) => new { item, index })
        var select1 = Expression.Call(enumerableSelectIndex, getItem, innerExpression1);
        // Inner function end

        // Enumerable.Select without indexer (generic)
        var enumerableSelectTSourceTResult = (from x in typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
                                              where x.Name == "Select" && x.IsGenericMethod
                                              let args = x.GetGenericArguments()
                                              where args.Length == 2
                                              let pars = x.GetParameters()
                                              where pars.Length == 2 &&
                                                  pars[0].ParameterType == typeof(IEnumerable<>).MakeGenericType(args[0]) &&
                                                  pars[1].ParameterType == typeof(Func<,>).MakeGenericType(args[0], args[1])
                                              select x).Single();

        // Enumerable.Select without indexer (non-generic)
        var enumerableSelect = enumerableSelectTSourceTResult.MakeGenericMethod(AnonymousType, typeof(int));

        // Inner function start
        ParameterExpression anonymousType1 = Expression.Parameter(AnonymousType, "x");
        var innerExpression2 = Expression.Lambda(Expression.Property(anonymousType1, "index"), anonymousType1);
        // Inner function end

        // .Select((previous select), x => x.index)
        var select2 = Expression.Call(enumerableSelect, select1, innerExpression2);

        // Enumerable.ToList (generic)
        var enumerableToListTSource = (from x in typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
                                       where x.Name == "ToList" && x.IsGenericMethod
                                       let args = x.GetGenericArguments()
                                       where args.Length == 1
                                       let pars = x.GetParameters()
                                       where pars.Length == 1 &&
                                           pars[0].ParameterType == typeof(IEnumerable<>).MakeGenericType(args[0])
                                       select x).Single();

        // Enumerable.ToList (non-generic)
        var enumerableToList = enumerableToListTSource.MakeGenericMethod(typeof(int));

        // .ToList((previous select))
        var toList1 = Expression.Call(enumerableToList, select2);

        var exp1 = Expression.Lambda<Func<Data, List<int>>>(toList1, p1);
        var func1 = exp1.Compile();

        // Test
        var data = new Data();
        data.S[new int[] { 14, 5 }] = new List<double> { 1.0, 2.0 };
        var result = func1(data);
    }
}

请注意,有一些限制:使用的匿名类型必须在编译时知道。使用Tuple&lt;&gt; 通常是另一种选择。在代码中,Type AnonymousType 行使编译器知道类型并获取它(通过最后的.GetType())。

另一个重要部分是关于在Enumerable 类中查找函数的部分。 Select 尤其是查找起来相当复杂,因为有两个不同的Select 具有相同数量的参数。

【讨论】:

  • 我需要将上面的 Linq 查询更改为下面的表达式:exp1 = p2 =&gt; p2.s[new int[] { 14, 5 }].Select((item, index) =&gt; new { item, index }).where(x=&gt;x.index&gt;2).Select(x =&gt; x.index).ToList(); 如何将 LINQ 表达式的where 部分转换为反射。
  • @grishmashah 我在这里采用了一条关于 SO 的基本规则:一个问题,问题没有变化,尤其是在 6 天之后。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-13
  • 1970-01-01
  • 2022-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多