IEnumerable.Select() when attribute is known only at runtime

时间:2019-04-16 22:28:30

标签: c# linq select

Say I have a data class like this and a list of its objects:

public class DataSet
{
    public int A { get; set; }
    public string B { get; set; }
    public double C { get; set; }
}

var data = new List<DataSet>
{
    new DataSet() { A = 1, B = "One", C = 1.1 },
    new DataSet() { A = 2, B = "Two", C = 2.2 },
    new DataSet() { A = 3, B = "Three", C = 3.3 }
};

I would like to do a Select() on the list, based on different properties. For example, if I need a list of property A, I could do this easily:

var listA = data.Select(x => x.A).ToList();

All good so far.

But in my program, I need to do the above, only, I wouldn't know whether I need a list of A or B or C until runtime. This 'knowledge' of what to select is stored in a list of strings, and I need to iterate it and extract only the appropriate lists. Something like this:

// GetKeys() will return the keys that I need to extract.
// So at one time keyList could have "A" and "B", another time "B" and "C" etc.
List<string> keyList = GetKeys();

foreach (var key in keyList)
{
    // What do I do here?
    data.Select(x =>???).ToList();
}

Is this possible at all? I'm fine with even a non-LINQ solution, if it achieves my goal.


EDIT: Clarifying the requirement.

The end result I want is a separate list based on each 'key' mentioned above. So, something like

List<List<object>>

The count in outer list would be the count of keyList. The inner list would have as many items as in DataSet.

3 个答案:

答案 0 :(得分:3)

This would probably not be the most efficient solution, but you could use Reflection for a fully dynamic solution:

private static List<List<object>> SelectDynamicData<T>(IEnumerable<T> data, List<string> properties)
{
    // get the properties only once per call
    // this isn't fast
    var wantedProperties = typeof(T)
        .GetProperties()
        .Where(x => properties.Contains(x.Name))
        .ToArray();

    var result = new Dictionary<string, List<object>>();

    foreach (var item in data)
    {
        foreach (var wantedProperty in wantedProperties)
        {
            if (!result.ContainsKey(wantedProperty.Name))
            {
                result.Add(wantedProperty.Name, new List<object>());
            }

            result[wantedProperty.Name].Add(wantedProperty.GetValue(item));
        }
    }

    return result.Select(x => x.Value).ToList();
}

And, of course, you'd need to do a double foreach or a LINQ query to print that. For example:

var data = new List<DataSet>
{
    new DataSet() { A = 1, B = "One", C = 1.1 },
    new DataSet() { A = 2, B = "Two", C = 2.2 },
    new DataSet() { A = 3, B = "Three", C = 3.3 }
};

var selectedData = SelectDynamicData(data, new List<string> { "A", "C" });

foreach (var list in selectedData)
{
    foreach (object item in list)
    {
        Console.Write(item + ", ");
    }
    Console.WriteLine();
}

答案 1 :(得分:3)

Using Creating Expression Trees by Using the API you can build an expression tree to represent the linq query you were hard coding in order to make it more dynamic.

Expression<Func<TModel, object>> GetPropertyExpression<TModel>(string propertyName) {
    // Manually build the expression tree for 
    // the lambda expression v => v.PropertyName.

    // (TModel v) =>
    var parameter = Expression.Parameter(typeof(TModel), "v");
    // (TModel v) => v.PropertyName
    var property = Expression.Property(parameter, propertyName);
    // (TModel v) => (object) v.PropertyName
    var cast = Expression.Convert(property, typeof(object));

    var expression = Expression.Lambda<Func<TModel, object>>(cast, parameter);
    return expression;
}

Review the comments to understand the building of the expression tree.

This now can be used with the data to extract the desired result.

Following similar to what was provided in another answer it would be simplified to

List<List<object>> SelectDynamicData<T>(IEnumerable<T> data, List<string> properties) {
    return properties
        .Select(_ => data.Select(GetPropertyExpression<T>(_).Compile()).ToList())
        .ToList();
}

Both methods are displayed in the following example

[TestMethod]
public void TestMethod1() {
    var data = new List<DataSet>
    {
        new DataSet() { A = 1, B = "One", C = 1.1 },
        new DataSet() { A = 2, B = "Two", C = 2.2 },
        new DataSet() { A = 3, B = "Three", C = 3.3 }
    };
    var propertyKnownAtRuntime = "A";
    var expression = GetPropertyExpression<DataSet>(propertyKnownAtRuntime);

    var listA = data.Select(expression.Compile()).ToList();
    //Produces
    // { 1, 2, 3}

    var listAC = SelectDynamicData(data, new List<string> { "A", "C" });
    //Produces
    //{
    //  { 1, 2, 3},
    //  { 1.1, 2.2, 3.3 }
    //}                
}

答案 2 :(得分:1)

You can use reflection, for example

        string key = "A";

        var query = data.Select(x =>
        {
            var prop = x.GetType().GetProperty(key); //NOTE: if key does not exist this will return null
            return prop.GetValue(x);
        });

        foreach (var value in query)
        {
            Console.WriteLine(value); //will print 1, 2, 3
        }