Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Let me preface this by saying, I am noob, and I know not what I do. So, if there is a better way of doing this, I am all ears.

Currently, I am working on project for which I need to be able to coerce a data source into a List<T>, where T is an anonymous type, and filter it using lambda expressions, or create lambda expressions on the fly, save them to a database. I have already created a static wrapper class for System.Linq.Dynamic.Core, called RunTimeType, that has methods which allow me to create an anonymous type from some data source, and then create a List<> of that anonymous type. After both of the anontype and List<anontype> are created, I am using an existing fluent interface to create an Expression<Func<T, bool>>. Once I build the Expression and compile it, I either want to execute it, or I want to convert it to a string and save it to a database, xml file, etc., for later use.

Case 1:

When compiling and then immediately executing the expression, I am good up until this line:

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

where i recieve the following error:

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.

This makes sense, because filter is declared as a dynamic, which I am forced to do, otherwise compiler would complain with the following:

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?)

Case2:

As for the case of building the expression, converting it to a string, and then compiling to a valid Func<T,TResult>, I am good up until this line:

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

where i recieve the following error:

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

However, I know that if I don't explicity cast to Func<dynamic, bool>, the compiler will complain with the following:

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

So, my question is, how do I get around both of these situations, while still maintaining the ability use the anonymous type. Just to clarify again, I am forced to create an anonymous type because, I will not know what data set that I will be getting at run time, as these data sets are completely dynamic.

I want to reiterate, that I am open to doing this in a different way as long as the project's constraints are met. Frankly, I have been working on this for a while, I am out of ideas, and I need some guidance.

Below is all of the relevant code.

Test code:

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

        }

    }   

}

RunTimeType Class:

(for brevity, I have omitted the the overloads for the Create and CreateGenericList methods)

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


    } 

} 

Update:

I incorporated @CSharpie's answer, and tailored it to fit my implementation. Everything compiles, however, I am not getting the correct output (see comments in code body).

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


    }

Final Update:

For those that are interested, I finally got this working. I figured out that my CreateGenericList method, was returning a list of only the first instance of my anonymous type. Saying that CreateGenericList should be become:

Reference: 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;

    }

And then Main becomes:

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

}
like image 474
rickmanalexander Avatar asked Jan 04 '20 19:01

rickmanalexander


2 Answers

Here is a simple example without any additional nuget packages that calls the Enumerable.Where method. I don't know what packages you are using exactly so you have to adept this to your requirements.

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

The thing here is, you need to construct the generic method yourself, which means you have to provide the generic arguments. In your case its the anonType. That is what this line is doing

MethodInfo finalMethod = whereMethod.MakeGenericMethod(anonType);

Afther that I use System.Linq.Expressions to create a simple Func<anontype, bool> which represents the filter.

Update

This works for me noiw, the flaw must be that other stuff you are doing. I leave that up to you to figure out.

     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();
like image 199
CSharpie Avatar answered Nov 09 '22 15:11

CSharpie


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.

like image 1
T_D Avatar answered Nov 09 '22 16:11

T_D