Sum up all the properties of a collection and dynamically assigned it to another object

此生再无相见时 提交于 2020-01-30 08:54:26

问题


I have a collection of object in lst of type DataResponse and what I would like to do is sum up all the properties that are int and decimal of this collection and assign the result of each property to another object DataContainerResponse that has the same exact property names(and types) as the those that are being summed up.

I can do this manually by typing out each property by hand and do a .Sum(s=>s.<propertyname>. But that so 90s. Below is my fruitless attempt to juice it out. Frankly, I never assigned a var to a lambda expression before and I don't even know if it's possible .Sum(s=><var name>);

 public DataAggragationResponse doAggregation(List<DataResponse> lst)
    {
        if (lst.Count == 0)
            return null;
        DataContainerResponse rd = new DataContainerResponse();

        //If I do it manually typing each prop by hand.
        rd.VIOL = lst.Sum(s => s.VIOL);


        //Automation!!!
        foreach (PropertyInfo propertyInfo in typeof(DataResponse).GetProperties())
        {

                rd.GetType().GetProperties().SetValue(lst.Sum(s => propertyInfo.Name[0]));

        }
    }

回答1:


If you want to go with full reflection, you can try something like the following. I didnt optimize the code, did it as fast as I can. So sorry for the messy look and Im assuming the property names are same in the aggregated result class and the unit class that you are aggregating against.

class Program
{
    static void Main(string[] args)
    {
        var list = new List<DataResponse>();
        list.Add(new DataResponse() { Stuff = 1, Stuff2 = 2 });
        list.Add(new DataResponse() { Stuff = 1, Stuff2 = 2 });

        Stopwatch watch = new Stopwatch();
        watch.Start();
        var response = DoAggregationReflection(list);
        watch.Stop();
        Console.WriteLine(watch.Elapsed.TotalMilliseconds);

        watch.Reset();

        watch.Start();
        var response2 = DoAggregation(list);
        watch.Stop();
        Console.WriteLine(watch.Elapsed.TotalMilliseconds);
    }

    public static DataAggragationResponse DoAggregationReflection(List<DataResponse> lst)
    {
        if (lst.Count == 0)
            return null;
        DataAggragationResponse aggrResponse = new DataAggragationResponse();
        var responseType = typeof(DataResponse);
        var aggrResponseType = typeof(DataAggragationResponse);

        foreach (PropertyInfo propertyInfo in typeof(DataResponse).GetProperties())
        {
            aggrResponseType.GetProperty(propertyInfo.Name).SetValue(aggrResponse, lst.Sum(x => (int)responseType.GetProperty(propertyInfo.Name).GetValue(x)));
        }

        return aggrResponse;
    }

    public static DataAggragationResponse DoAggregation(List<DataResponse> lst)
    {
        if (lst.Count == 0)
            return null;
        DataAggragationResponse aggrResponse = new DataAggragationResponse();

        aggrResponse.Stuff = lst.Sum(x => x.Stuff);
        aggrResponse.Stuff2 = lst.Sum(x => x.Stuff2);

        return aggrResponse;
    }
}


public class DataResponse
{
    public int Stuff { get; set; }
    public int Stuff2 { get; set; }
}

public class DataAggragationResponse
{
    public int Stuff { get; set; }
    public int Stuff2 { get; set; }
}

But, as a suggestion, if you want to go with this approach, its better if you can cache all the reflection invokes you're making as they are costly. And the 90's approach would still win in benchmark. Like the example above would benchmark like the following with the simple StopWatch.

1.8193
0.4476
Press any key to continue . . .

The first one is the execution time of DoAggregationReflection and the last one is the execution time of DoAggregation. You can optimize the reflection one as much as you want but I think it would still fail to compete with the basic one.

Sometime's the 90's are way better. ;) Although you'd still use LINQ to do the actual summation so that's not that 90's anymore as LINQ was born in 2007 according to wikipedia.




回答2:


Hopefully this can help you. I wish I had kept the SO link to the question I pulled this from a while ago. Sorry to the original poster for not mentioning his/her name.

using System.Reflection;
public static Dictionary<string, string> GetPropertiesValue(object o)
    {
        Dictionary<string, string> PropertiesDictionaryToReturn = new Dictionary<string, string>();

        foreach (MemberInfo itemMemberInfo in o.GetType().GetMembers())
        {
            if (itemMemberInfo.MemberType == MemberTypes.Property)
            {
                //object PropValue = GetPropertyValue(OPSOP, item.Name);
                //string itemProperty = itemMemberInfo.Name;
                //string itemPropertyValue = o.GetType().GetProperty(itemMemberInfo.Name).GetValue(o, null).ToString();
                //Console.WriteLine(itemProperty + " : " + itemPropertyValue);
                PropertiesDictionaryToReturn.Add(itemMemberInfo.Name, o.GetType().GetProperty(itemMemberInfo.Name).GetValue(o, null).ToString());
            }
        }
        return PropertiesDictionaryToReturn;
    }

It's not exactly what you need but, I think you could adapt it.




回答3:


I would rather take a different approach. I would dynamically build and compile (once) something like this:

Func<DataContainerResponse, DataResponse, DataContainerResponse> aggregateFunc =
(result, item) =>
{
    result.Prop1 += item.Prop1;
    result.Prop2 += item.Prop2;
    ...
    result.PropN += item.PropN;
    return result;
}

(if you wonder why the signature is like the above, the answer is - because it can be used directly for the following Aggregate overload).

Here is how it can be done:

static readonly Func<DataContainerResponse, DataResponse, DataContainerResponse> 
    AggregateFunc = BuildAggregateFunc();

static Func<DataContainerResponse, DataResponse, DataContainerResponse> BuildAggregateFunc()
{
    var result = Expression.Parameter(typeof(DataContainerResponse), "result");
    var item = Expression.Parameter(typeof(DataResponse), "item");
    var propertyTypes = new HashSet<Type> { typeof(decimal), typeof(int) };
    var statements = item.Type.GetProperties()
            .Where(p => propertyTypes.Contains(p.PropertyType))
            .Select(p => Expression.AddAssign(
                Expression.Property(result, p.Name),
                Expression.Property(item, p)));
    var body = Expression.Block(statements
        .Concat(new Expression[] { result }));
    var lambda = Expression.Lambda<Func<DataContainerResponse, DataResponse, DataContainerResponse>>(
        body, result, item);
    return lambda.Compile();
}

and the usage is simple:

public DataContainerResponse DoAggregation(List<DataResponse> source)
{
    return source.Aggregate(new DataContainerResponse(), AggregateFunc);
}


来源:https://stackoverflow.com/questions/39107094/sum-up-all-the-properties-of-a-collection-and-dynamically-assigned-it-to-another

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!