C# LINQ build expression with anonymous type
Asked Answered
T

1

8

I have code which builds list only with one property "Name". How to modify the code so it can build list with two properties "Name" and "Test_Result" I know that anonymous type can be used to perform this, but how to put them to dynamic expression? here is my code:

string item = "Name";
string item2 = "Test_Result";
Type studentType = typeof(Student);

ParameterExpression itemParam = Expression.Parameter(studentType, item);
MemberInfo itemProperty = studentType.GetProperty(item);

MemberExpression valueInItemField = 
    Expression.MakeMemberAccess(itemParam, itemProperty);

Expression<Func<Student, string>> selectExpression =
    Expression<Func<Student, string>>
        .Lambda<Func<Student, string>>(valueInItemField, itemParam);

IEnumerable<string> currentItemFields = 
    DeserializedStudents.Select(selectExpression.Compile());
Thee answered 8/1, 2013 at 11:46 Comment(1)
Re your most recent repeat of the same; please clarify what is still unclear after the answers here and on your earlier question. If you tell us what is unclear, we can probably clarify.Enzymology
E
30

I'm assuming that the "Name" and "Test_Result" here are flexible and cannot be hard-coded.

Anonymous types are fully defined regular classes; the only interesting thing about them is that the compiler provides the details instead of you.

I would suggest that the way to handle this scenario would be to use Tuple.Create to create an IEnumerable<Tuple<string,string>> and refer to them as Item1, Item2 (the names from Tuple<,>. The other option would be to use something like ExpandoObject, and then use either the IDictionary<string,object> API, or the dynamic API, to get the values back out.

For example:

string item1 = "Name";
string item2 = "Test_Result";
Type studentType = typeof(Student);

var itemParam = Expression.Parameter(studentType, "x");
var member1 = Expression.PropertyOrField(itemParam, item1);
var member2 = Expression.PropertyOrField(itemParam, item2);
var selector = Expression.Call(typeof(Tuple), "Create",
    new[] { member1.Type, member2.Type }, member1, member2);
var lambda = Expression.Lambda<Func<Student, Tuple<string,string>>>(
    selector, itemParam);

var currentItemFields = students.Select(lambda.Compile());

Here's the same projecting into a custom type with members name and result:

class ProjectedData
{
    public string name { get; set; }
    public string result { get; set; }
}

...

string item1 = "Name";
string item2 = "Test_Result";
Type studentType = typeof(Student);

var itemParam = Expression.Parameter(studentType, "x");
var member1 = Expression.PropertyOrField(itemParam, item1);
var member2 = Expression.PropertyOrField(itemParam, item2);
var selector = Expression.MemberInit(Expression.New(typeof(ProjectedData)),
    Expression.Bind(typeof(ProjectedData).GetMember("name").Single(), member1),
    Expression.Bind(typeof(ProjectedData).GetMember("result").Single(), member2)
);
var lambda = Expression.Lambda<Func<Student, ProjectedData>>(
    selector, itemParam);

var currentItemFields = students.Select(lambda.Compile());

Or for the approach using a dictionary:

string[] fields = {"Name", "Test_Result"};
Type studentType = typeof(Student);

var itemParam = Expression.Parameter(studentType, "x");

var addMethod = typeof(Dictionary<string, object>).GetMethod(
    "Add", new[] { typeof(string), typeof(object) });
var selector = Expression.ListInit(
        Expression.New(typeof(Dictionary<string,object>)),
        fields.Select(field => Expression.ElementInit(addMethod,
            Expression.Constant(field),
            Expression.Convert(
                Expression.PropertyOrField(itemParam, field),
                typeof(object)
            )
        )));
var lambda = Expression.Lambda<Func<Student, Dictionary<string,object>>>(
    selector, itemParam);

var currentItemFields = students.Select(lambda.Compile());
Enzymology answered 8/1, 2013 at 11:50 Comment(7)
i mean i would like to change var students = DeserializedStudents.Select(cust => new { name = cust.Name, result = cust.Test_Result }); to dynamic expressionThee
@Thee you would have to define your own type is the point; if you are happy to define some type somewhere with members name and result, then fine: just create a class. I can add an example of that in a moment (done).Enzymology
Could you tell me please one more thing? What should I do if don't know how many items i will need to select. I mean one time it can be two items, next time it can be four items and so on. Should i create some list with that items or what?Thee
@Thee personally I would populate a dictionary with the values. Do you want an example?Enzymology
@Thee added at the bottomEnzymology
hi Mark! what if I need almost the same but want the result as list of anonymous types? For example how I can simulate situation like this Expression<Func<Student, object>> exp = s => new { Name = s.Name, Result= s.Test_Result }; in order to use it late somewhere like this students.Select(exp).ToList();Shewchuk
@mr-pumpkin did you find the answer to your problem? I'm now having something similar, but just the s.Name and s.Test_Result are not hard coded and they get from an input during runtime as a list of stringToad

© 2022 - 2024 — McMap. All rights reserved.