如何处理匿名类型 <T> 的铸造委托,以委托 T 以用于 IEnumerable<T> 的 Where<T>() 方法

How to handle casting delegate of anonymous type <T>, to delegate of T for use in Where<T>() method of an IEnumerable<T>

让我先说一句,我是菜鸟,我不知道我在做什么。所以,如果有更好的方法来做到这一点,我会洗耳恭听。

目前,我正在做一个项目,我需要能够将数据源强制转换为 List<T>,其中 T 是匿名类型,并使用 lambda 表达式对其进行过滤,或者即时创建 lambda 表达式,将它们保存到数据库中。我已经为 System.Linq.Dynamic.Core 创建了一个名为 RunTimeType 的静态包装器 class,它具有允许我从某些数据源创建匿名类型的方法,然后创建一个 List<> 那种匿名类型。在创建 anontypeList<anontype> 之后,我正在使用 existing fluent interface 创建 Expression<Func<T, bool>>。一旦我构建 Expression 并编译它, 我要么想执行它,要么想把它转换成字符串保存到数据库,xml文件等,以备后用。

案例一:

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

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

我收到以下错误:

Error CS1973 C# has no applicable method named 'Where' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.

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

Error CS1061 'object' does not contain a definition for 'By' and no accessible extension method 'By' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)

案例2:

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

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

我收到以下错误:

Error System.InvalidCastException 'System.Func2[<>f__AnonymousType02[System.String,System.String],System.Boolean]' to type 'System.Func`2[System.Object,System.Boolean]'.'

但是,我知道如果我不显式转换为 Func<dynamic, bool>,编译器会报错如下:

Error 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();

        }

    }   

}

运行时间类型Class:

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

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

        }

        //
        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();

}

这是您看似想要执行的操作的最小工作代码:

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

我认为这还没有解决您的问题,所以也许您可以详细说明我的简单示例,哪些事情您无法控制,或者我错过了您问题的哪些细微之处。

例如,我不确定您的 var lamda = filter.GetExpression(); 是否可以 return Func<dynamic,bool> 或者是否已经可以。

这是一个简单的例子,没有任何调用 Enumerable.Where 方法的额外 nuget 包。 我不知道你使用的是什么包,所以你必须根据你的要求来使用它。

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

public class Program
{
    static void Main(string[] args)
    {
        var test = new {Foo = "bar"};
        var test2 = new {Foo = "derp"};

        // get the annonymous type
        Type anonType = test.GetType();


        // create a list of that annonymous type
        IList genericList = (IList) Activator.CreateInstance(typeof(List<>).MakeGenericType(anonType));

        genericList.Add(test);
        genericList.Add(test2);




        // Find the correct Enumerable.Where method
        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;
        });

        // construct the finalmethod using generic type
        MethodInfo finalMethod = whereMethod.MakeGenericMethod(anonType);

        // define the Type of the Filter Func<anontype,bool>
        Type filterType = typeof(Func<,>).MakeGenericType(anonType, typeof(bool));


        // Build a simple filter expression
        // this is mostly to subsitute for the missing packages you are using to create that filter func
        ParameterExpression parameter = Expression.Parameter(anonType, "item");
        MemberExpression member = Expression.Property(parameter, "Foo");
        BinaryExpression euqalExpression = Expression.Equal(member, Expression.Constant("derp"));

        LambdaExpression filterExpression = Expression.Lambda(filterType, euqalExpression, parameter);
        Delegate filter = filterExpression.Compile();

        Console.WriteLine("This is the Filter: {0}", filterExpression);





        // Finally invoke and see it in action
        IEnumerable result = (IEnumerable) finalMethod.Invoke(null, new object[] {genericList, filter});

        foreach (dynamic o in result)
        {
            Console.WriteLine(o.Foo);
        }

        Console.ReadKey();
    }
}

这里的问题是,您需要自己构造泛型方法,这意味着您必须提供泛型参数。在您的情况下,它是 anonType。 这就是这条线所做的

MethodInfo finalMethod = whereMethod.MakeGenericMethod(anonType);

然后我使用 System.Linq.Expressions 创建一个简单的 Func<anontype, bool> 代表过滤器。

更新

这对我没用,缺陷一定是你正在做的其他事情。我把这个留给你自己解决。

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

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

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

     var anonType = RunTimeType.Create(arrayTest);

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




     // define the Type of the Filter Func<anontype,bool>
     Type filterType = typeof(Func<,>).MakeGenericType(anonType, typeof(bool));


     // Build a simple filter expression
     ParameterExpression parameter = Expression.Parameter(anonType, "item");


     var property = anonType.GetProperty("Field1");
     MemberExpression member = Expression.Property(parameter, property);


     BinaryExpression euqalExpression = Expression.Equal(member, Expression.Constant("derp"));

     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);
     LambdaExpression filterExpression = Expression.Lambda(filterType, euqalExpression, parameter);



     Delegate filter = filterExpression.Compile();


     Console.WriteLine("This is the Filter: {0}", filterExpression);

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

     foreach (dynamic o in result)
     {
         Console.WriteLine(o.Field1);
     }

     Console.ReadKey();