首页 > 解决方案 > 如何处理匿名类型的铸造委托, 委托 T 用于 WhereIEnumerable 的 () 方法

问题描述

让我先说,我是菜鸟,我不知道我在做什么。因此,如果有更好的方法可以做到这一点,我会全力以赴。

目前,我正在开发一个项目,我需要能够将数据源强制转换为匿名类型,并使用 lambda 表达式对其进行过滤,或者动态创建 lambda 表达式,将它们保存到数据库中List<T>T我已经创建了一个System.Linq.Dynamic.Core名为的静态包装类RunTimeType,它具有允许我从某个数据源创建匿名类型的方法,然后创建List<>该匿名类型的一个。在创建anontype和之后List<anontype>,我使用现有的流利界面来创建Expression<Func<T, bool>>. 一旦我构建Expression并编译它,我要么想执行它,要么想将它转换为字符串并将其保存到数据库、xml 文件等中,以供以后使用。

情况1:

编译然后立即执行表达式时,我一直很好,直到这一行:

var testList = anonList.Where(castedExp).ToList();

我收到以下错误:

错误 CS1973 C# 没有名为“Where”的适用方法,但似乎具有该名称的扩展方法。扩展方法不能动态调度。考虑强制转换动态参数或在没有扩展方法语法的情况下调用扩展方法。

这是有道理的,因为filter被声明为 a dynamic,我不得不这样做,否则编译器会抱怨以下内容:

错误 CS1061 'object' 不包含 'By' 的定义,并且找不到接受“object”类型的第一个参数的可访问扩展方法“By”(您是否缺少 using 指令或程序集引用?)

案例2:

至于构建表达式,将其转换为字符串,然后编译为有效的情况Func<T,TResult>,我很好,直到这一行:

var castedExp = (Func<dynamic, bool>)compileExp;

我收到以下错误:

错误 System.InvalidCastException 'System.Func 2[<>f__AnonymousType02[System.String,System.String],System.Boolean]' 键入 'System.Func`2[System.Object,System.Boolean]'。

但是,我知道如果我没有明确地转换为Func<dynamic, bool>,编译器会抱怨以下内容:

错误 CS1503 Argument 2: cannot convert from 'System.Delegate' to 'System.Func<dynamic, bool>'

所以,我的问题是,我如何解决这两种情况,同时仍然保持使用匿名类型的能力。再次澄清一下,我被迫创建一个匿名类型,因为我不知道在运行时我将获得什么数据集,因为这些数据集是完全动态的。

我想重申,只要项目的约束得到满足,我愿意以不同的方式这样做。坦率地说,我已经为此工作了一段时间,我没有想法,我需要一些指导。

以下是所有相关代码。

测试代码:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using ExpressionBuilder.Generics;
using ExpressionBuilder.Common;
using System.Linq;
using System.Linq.Dynamic;
using System.Linq.Dynamic.Core;
using ExpressionBuilterTest.TestImplementations;

namespace ExpressionBuilterTest
{
    class Program
    {


        static void Main(string[] args)
        {   

            //test Data source
            object[,] arrayTest = new object[3, 2];

            arrayTest[0, 0] = "Field1";
            arrayTest[1, 0] = "X1";
            arrayTest[2, 0] = "Y1";

            arrayTest[0, 1] = "Field2";
            arrayTest[1, 1] = "X2";
            arrayTest[2, 1] = "Y2";

            var anonType = RunTimeType.Create(arrayTest);

            var anonList = RunTimeType.CreateGenericList(anonType, arrayTest); 

            //Creation of List<anonymous> type
            var anonList = CreateGenericList(anonType, arrayTest);

            //Creation of List<anonymous> type
            Type genericFilter = typeof(Filter<>);

            Type constructedClass = genericFilter.MakeGenericType(anonType);


            //*************************Case 1*************************
            /*
            use dynamic otherwise compiler complains about accessing
            methods on the instance of the filter object
            */ 
            dynamic filter = Activator.CreateInstance(constructedClass);

            filter.By("Field1", Operation.Contains, " X1 ")
                  .Or.By("Field2", Operation.Contains, " X2 ");

            //returns Expression<Func<T, bool>>
            var lamda = filter.GetExpression();

            //Error CS1973 
            IEnumerable<dynamic> testList = anonList.Where(castedExp).ToList();

            Console.WriteLine(testList.Count().ToString());
            Console.WriteLine("\n");



            //*************************Case 2*************************
            //convert to string
            string expString = lamda.Body.ToString().Replace("AndAlso", "&&").Replace("OrElse", "||");

            // simulation of compiling an  expression from a string which would be returned from a database
            var param = Expression.Parameter(anonType, ExpressionParameterName.Parent);

            var exp = System.Linq.Dynamic.DynamicExpression.ParseLambda(new ParameterExpression[] { param }, typeof(bool), expString);

            var compiledExp = exp.Compile();

            //*******************************************************
            //Error CS1973
            'System.Func`2[<>f__AnonymousType0`2[System.String,System.String],System.Boolean]' to type 'System.Func`2[System.Object,System.Boolean]'.'
            var castedExp = (Func<dynamic, bool>)compileExp;
            //*******************************************************

            var testList2 = anonList.Where(castedExp).ToList(); 

            Console.WriteLine(testList2.Count().ToString());
            Console.ReadKey();

        }

    }   

}

运行时间类型类:

(为简洁起见,我省略了CreateandCreateGenericList方法的重载)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Runtime.CompilerServices;

namespace ExpressionBuilterTest.TestImplementations
{
    public static class  RunTimeType
    {

        /// <summary>
        /// Creates an anonymous type from a 2d array that includes headers
        /// </summary>
        public static Type Create<T>(T[,] fieldNameAndValues)
        {
            IList<System.Linq.Dynamic.Core.DynamicProperty> properties = new List<System.Linq.Dynamic.Core.DynamicProperty>();

            int columnCount = fieldNameAndValues.GetLength(1); 

            for (int jj = 0; jj < columnCount; jj++)
                properties.Add(new System.Linq.Dynamic.Core.DynamicProperty(fieldNameAndValues[0, jj].ToString(), fieldNameAndValues[1, jj].GetType()));

            return DynamicClassFactory.CreateType(properties);

        }

        /// <summary>
        /// Creates an IEnumerable<dynamic>, where dynamic is an anonymous type, from a 2d array
        /// </summary>
        /// <param name="type">Anonymous type</param>
        /// <param name="data">2 dimensional array of data</param>
        public static IEnumerable<dynamic> CreateGenericList<T>(Type anonType, T[,] data)
        {
            ThrowIfNotAnonymousType(anonType); 

            dynamic dynoObject = Activator.CreateInstance(anonType);

            var fieldNames = dynoObject.GetDynamicMemberNames();

            Type genericListType = typeof(List<>);

            Type constructedClass = genericListType.MakeGenericType(anonType);

            dynamic list = (IEnumerable<dynamic>)Activator.CreateInstance(constructedClass);

            int rowCount = data.GetLength(0);
            int jj;

            for (int ii = 1; ii < rowCount; ii++)   //skip first row
            {

                jj = 0;

                foreach (var field in fieldNames)
                    anonType.GetProperty(field).SetValue(dynoObject, data[ii, jj], null);
                jj++;

                list.Add(dynoObject);

            }

            return list;

        }

        private static void ThrowIfNotAnonymousType(Type type)
        {
            if (!IsAnonymousType(type))
                throw new Exception("'anonType' must be an anonymous type");

        }

        //https://stackoverflow.com/questions/1650681/determining-whether-a-type-is-an-anonymous-type
        private static Boolean IsAnonymousType(Type type)
        {
            Boolean hasCompilerGeneratedAttribute = type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Count() > 0;
            Boolean nameContainsAnonymousType = type.FullName.Contains("AnonymousType");
            Boolean isAnonymousType = hasCompilerGeneratedAttribute && nameContainsAnonymousType;

            return isAnonymousType;
        }


    } 

} 

更新:

我合并了@CSharpie 的答案,并对其进行了定制以适合我的实现。一切都可以编译,但是,我没有得到正确的输出(请参阅代码正文中的注释)。

    static void Main(string[] args)
    {

        object[,] arrayTest = new object[3, 2];

        arrayTest[0, 0] = "Field1";
        arrayTest[1, 0] = "X1";
        arrayTest[2, 0] = "Y1";

        arrayTest[0, 1] = "Field2";
        arrayTest[1, 1] = "X2";
        arrayTest[2, 1] = "Y2";

        var anonType = RunTimeType.Create(arrayTest);

        var anonList = RunTimeType.CreateGenericList(anonType, arrayTest);


        Type targetType = anonType;

        Type genericFilter = typeof(Filter<>);

        Type constructedClass = genericFilter.MakeGenericType(targetType);

        dynamic filter = Activator.CreateInstance(constructedClass);

        //Dynamically build expression
        filter.By("Field1", Operation.Contains, "X")
            .Or.By("Field2", Operation.Contains, "2");

        //Returns Expression<Func<anonType, bool>>
        var lamda = filter.GetExpression();

        string expString = lamda.Body.ToString();
        expString = expString.Replace("AndAlso", "&&").Replace("OrElse", "||");

        /*
        Prints: (((x.Field1 != null) && x.Field1.Trim().ToLower().Contains("X".Trim().ToLower())) || ((x.Field2 != null) && 
                    x.Field2.Trim().ToLower().Contains("2".Trim().ToLower())))
        */
        Console.WriteLine(expString);

        ParameterExpression param = Expression.Parameter(targetType, ExpressionParameterName.Parent);

        LambdaExpression exp = System.Linq.Dynamic.DynamicExpression.ParseLambda(new ParameterExpression[] { param }, typeof(bool), expString);

        Delegate compileExp = exp.Compile(); 


        MethodInfo whereMethod = typeof(Enumerable).GetMethods().Single(m =>
        {
            if (m.Name != "Where" || !m.IsStatic)
                return false;
            ParameterInfo[] parameters = m.GetParameters();
            return parameters.Length == 2 && parameters[1].ParameterType.GetGenericArguments().Length == 2;
        });

        MethodInfo finalMethod = whereMethod.MakeGenericMethod(anonType);

        IEnumerable resultList = (IEnumerable)finalMethod.Invoke(null, new object[] { anonList, compileExp });

        /*
         Prints Nothing but should print the following:
         X1 X2
        */
        foreach (dynamic val in resultList)
        {
            Console.WriteLine(val.Field1 + "/t" + val.Field2);
        }

        Console.ReadKey();


    }

最终更新:

对于那些感兴趣的人,我终于得到了这个工作。我发现我的CreateGenericList方法只返回了我的匿名类型的第一个实例的列表。说CreateGenericList应该变成:

参考:https ://github.com/StefH/System.Linq.Dynamic.Core/blob/master/src/System.Linq.Dynamic.Core/DynamicClass.cs

    /// <summary>
    /// Creates an IEnumerable<dynamic>, where dynamic is an anonymous type, from a 2d array
    /// </summary>
    /// <param name="type">Anonymous type</param>
    /// <param name="data">2 dimensional array of data</param>
    public static IEnumerable<dynamic> CreateGenericList<T>(Type anonType, T[,] data)
    {
        ThrowIfNotAnonymousType(anonType);

        Type genericListType = typeof(List<>);

        Type constructedClass = genericListType.MakeGenericType(anonType);

        dynamic list = (IEnumerable<dynamic>)Activator.CreateInstance(constructedClass);

        //first instance
        dynamic dynoObject = Activator.CreateInstance(anonType);

        //System.Linq.Dynamic.Core.DynamicClass.GetDynamicMemberNames()
        var fieldNames = dynoObject.GetDynamicMemberNames(); 

        int rowCount = data.GetLength(0);
        int jj;

        for (int ii = 1; ii < rowCount; ii++)   //skip first row
        {


            jj = 0;

            foreach (var field in fieldNames)
            {

            //System.Linq.Dynamic.Core.DynamicClass.SetDynamicPropertyValue()
                dynoObject.SetDynamicPropertyValue(field,data[ii, jj]);
                jj++;

            }
            list.Add(dynoObject);

            //create a new instance for each iteration of the loop
            dynoObject = Activator.CreateInstance(anonType);

        }

        return list;

    }

然后Main变成:

    static void Main(string[] args)
    {

        object[,] arrayTest = new object[3, 2];

        arrayTest[0, 0] = "Field1";
        arrayTest[1, 0] = "X1";
        arrayTest[2, 0] = "blah";

        arrayTest[0, 1] = "Field2";
        arrayTest[1, 1] = "Y1";
        arrayTest[2, 1] = "Y2";

        var anonType = RunTimeType.Create(arrayTest);

        var anonList = RunTimeType.CreateGenericList(anonType, arrayTest);

        Type genericFilter = typeof(Filter<>);

        Type constructedClass = genericFilter.MakeGenericType(anonType);

        dynamic filter = Activator.CreateInstance(constructedClass);


        //Dynamically build expression
        filter.By("Field1", Operation.Contains, "blah")
            .Or.By("Field2", Operation.Contains, "2");


        //Returns Expression<Func<anonType, bool>>
        var lamda = filter.GetExpression();

        //Prints: System.Func`2[<>f__AnonymousType0`2[System.String,System.String],System.Boolean]
        Console.WriteLine(lamda.Compile().ToString());
        Console.WriteLine("\n");

        string expBodyString = lamda.Body.ToString();

        /*
        Prints: (((x.Field1 != null) AndAlso x.Field1.Trim().ToLower().Contains("blah".Trim().ToLower())) 
                    OrElse ((x.Field2 != null) AndAlso x.Field2.Trim().ToLower().Contains("2".Trim().ToLower())))
        */ 
        Console.WriteLine(expBodyString);
        Console.WriteLine("\n");

        expBodyString = expBodyString.Replace("AndAlso", "&&").Replace("OrElse", "||");

        /*
        Prints: (((x.Field1 != null) && x.Field1.Trim().ToLower().Contains("blah".Trim().ToLower())) || ((x.Field2 != null) 
                    && x.Field2.Trim().ToLower().Contains("2".Trim().ToLower())))
        */
        Console.WriteLine(expBodyString);
        Console.WriteLine("\n");


        ParameterExpression param = Expression.Parameter(anonType, ExpressionParameterName.Parent);


        LambdaExpression exp = System.Linq.Dynamic.Core.DynamicExpressionParser.ParseLambda(new ParameterExpression[] { param }, typeof(bool), expBodyString);


        /*
        Prints: (((x.Field1 != null) AndAlso x.Field1.Trim().ToLower().Contains("blah".Trim().ToLower())) 
                    OrElse ((x.Field2 != null) AndAlso x.Field2.Trim().ToLower().Contains("2".Trim().ToLower())))
        */
        Console.WriteLine(exp.Body.ToString());
        Console.WriteLine("\n");


        Delegate compileExp = exp.Compile();

        //Prints: System.Func`2[<>f__AnonymousType0`2[System.String,System.String],System.Boolean]
        Console.WriteLine(compileExp.ToString());
        Console.WriteLine("\n");

        MethodInfo whereMethod = typeof(Enumerable).GetMethods().Single(m =>
        {
            if (m.Name != "Where" || !m.IsStatic)
                return false;
            ParameterInfo[] parameters = m.GetParameters();
            return parameters.Length == 2 && parameters[1].ParameterType.GetGenericArguments().Length == 2;
        });

        MethodInfo finalMethod = whereMethod.MakeGenericMethod(anonType);

        IEnumerable resultList = (IEnumerable)finalMethod.Invoke(null, new object[] { anonList, compileExp });


        //Prints: blah    Y2
        foreach (dynamic val in resultList)
        {
            Console.WriteLine(val.Field1 + "\t" + val.Field2);
        }

        Console.ReadKey();

}

标签: c#linqlambdadslanonymous-types

解决方案


Here is a minimal working code for what you seemingly want to do:

IEnumerable<dynamic> anonList = new dynamic[] {new {Test = "1"}, new {Test = "2"}};

Func<dynamic, bool> filterExpression = (d) => d.Test == "2";

var result = anonList.Where(filterExpression).ToList();

I assume that this does not solve your problem yet, so maybe you can elaborate on my simple example which things you do not have control of or what subtleties of your problem I missed.

For example I am not sure if your var lamda = filter.GetExpression(); can return Func<dynamic,bool> or if it does already.


推荐阅读