LINQ Sumar todas las propiedades

¿Existe una forma elegante de hacer esto?

Assume I am working with objects that have excusively numeric properties (ints and doubles), like this:

Foo
    -bar1 int
    -bar2 int
    -bar3 int
    -foobar1 double
    -foobar2 double

and I have a collection of List...is there a way to have it simply sum all the numeric properties in the List and return a single object Foo with all the totals?

thanks as always SO

preguntado el 04 de septiembre de 13 a las 02:09

I am trying to verify if I understood your question correctly. Do you want to obtain a single Foo instance of which each member value is the sum of corresponding member for a set of Foo instances that are in a list? -

this should be done with simple For loop, sometimes if you try to do it using LINQ, we may have to end up using ForEach method or something like that. -

3 Respuestas

If I understood your question correctly, and you want to get one Foo instance, in which each member is the sum of all the corresponding members of Foo instances that are present in a list, then one possible (somewhat verbose, but straightforward no magic involved) way is to use linq Aggregate:

// Simplified Foo to prevent a lot of typing.
public class Foo
{
    public int bar1 { get; set; }
    public int bar2 { get; set; }
    public double foobar1 { get; set; }
}

var myFooList = new List<Foo>
{
    new Foo() { bar1 = 1, bar2 = 2, foobar1 = 20.0 },
    new Foo() { bar1 = 5, bar2 = 8, foobar1 = 42.0 },
    new Foo() { bar1 = 9, bar2 = 3, foobar1 = -10.0 },
};
var myFooSum = myFooList.Aggregate(new Foo(), (curSum, foo) =>
{
    curSum.bar1 += foo.bar1;
    curSum.bar2 += foo.bar2;
    curSum.foobar1 += foo.foobar1;
    return curSum;
});
Console.WriteLine("FooSum: bar1 = {0}, bar2 = {1}, bar3 = {2}", myFooSum.bar1, myFooSum.bar2, myFooSum.foobar1);

Huellas dactilares:

FooSum: bar1 = 15, bar2 = 13, bar3 = 52

If I misunderstood, and you want so sum an arbitrary list of objects, some of which may contain multiple numerical fields, into a single sum value an approach such as suggested by @Simon Whitehead could work:

public class Bar
{
    public int baz { get; set; }
    public double booz { get; set; }
    public string nonNumeric { get; set; }
}

static double SumNumericalProperties(object obj)
{
    if (obj == null)
        return 0;
    if (obj is int)
        return (int)obj;
    if (obj is double)
        return (double)obj;
    // etc for other numeric types.

    var sum = 0.0;
    foreach (var prop in obj.GetType().GetProperties())
    {
        if (typeof(int).IsAssignableFrom(prop.PropertyType))
            sum += (int)prop.GetValue(obj);
        else if (typeof(double).IsAssignableFrom(prop.PropertyType))
            sum += (double)prop.GetValue(obj);
        // etc for other numeric types.
    }
    return sum;
}

var myObjectList = new List<object>
{
    new Foo() { bar1 = 1, bar2 = 2, foobar1 = 20.0 },
    new Bar() { baz = 10, booz = 100.0 },
    24,
    33.3333,
    new Foo() { bar1 = 5, bar2 = 8, foobar1 = 42.0 },
    new Foo() { bar1 = 9, bar2 = 3, foobar1 = -10.0 },
};

var myFooSum = myObjectList.Sum(SumNumericalProperties);
Console.WriteLine("Sum = {0}", myFooSum);

Que imprime:

Sum = 247.3333

Respondido el 04 de Septiembre de 13 a las 03:09

No creo que hayas entendido la pregunta. - Erik Funkenbusch

I think this is what the OP wants. Only 2 phrases can show that, return single Foo y all the totals - Rey rey

It can also be done using this one-liner :-)

Item sum = (from p in typeof(Item).GetFields()
          where Type.GetTypeCode(p.FieldType) == TypeCode.Int32 || Type.GetTypeCode(p.FieldType) == TypeCode.Double
          group p by p into g
          select new { Prop=g.Key, Sum=items.Sum(s => (decimal)Convert.ChangeType(g.Key.GetValue(s), TypeCode.Decimal)) })
          .Aggregate(new Item(), (state, next) => {  
            next.Prop.SetValue(state, Convert.ChangeType(next.Sum, next.Prop.FieldType));
            return state;
          });

Muestra completa:

using System;
using System.Linq;
using System.Collections.Generic;

class Program
{
    class Item
    {
        public int bar1 = 0;
        public int bar2 = 0;
        public double foobar1 = 0;
        public double foobar2 = 0;
    }

    public static void Main ()
    {
        var items = new List<Item>();
        items.Add(new Item() { bar1 = 1, bar2 = 2, foobar1 = 1.1, foobar2 = 1.2 });
        items.Add(new Item() { bar1 = 1, bar2 = 2, foobar1 = 1.1, foobar2 = 1.2 });
        items.Add(new Item() { bar1 = 1, bar2 = 2, foobar1 = 1.1, foobar2 = 1.2 });

        var sum = (from p in typeof(Item).GetFields()
                  where Type.GetTypeCode(p.FieldType) == TypeCode.Int32 || Type.GetTypeCode(p.FieldType) == TypeCode.Double
                  group p by p into g
                  select new { Prop=g.Key, Sum=items.Sum(s => (decimal)Convert.ChangeType(g.Key.GetValue(s), TypeCode.Decimal)) })
                  .Aggregate(new Item(), (state, next) => {  
                    next.Prop.SetValue(state, Convert.ChangeType(next.Sum, next.Prop.FieldType));
                    return state;
                  });


        Console.WriteLine("bar1: " + sum.bar1);
        Console.WriteLine("bar2: " + sum.bar2);
        Console.WriteLine("foobar1: " + sum.foobar1);
        Console.WriteLine("foobar2: " + sum.foobar2);
    }
}

Respondido el 04 de Septiembre de 13 a las 04:09

ding ding, we have a winner! Thank you for taking the time to understand the question and provide an elegant answer, that was exactly what I was looking for. Also you anticipated the next issue which was objects that might have mixed types (numeric and strings for example) - snappymcsnap

Rough attempt:

    private static double sumNumericalProperties<T>(T obj)
    {
        var result = 0d;

        foreach (var prop in typeof (T).GetProperties())
        {
            if (typeof(int).IsAssignableFrom(prop.PropertyType))
            {
                result += (int)prop.GetValue(obj);
            }
            else if (typeof(double).IsAssignableFrom(prop.PropertyType))
            {
                result += (double) prop.GetValue(obj);
            }
        }

        return result;
    }

Entrada de ejemplo:

class Foo
{
    public int Bar1 { get; set; }
    public int Bar2 { get; set; }
    public double Foobar1 { get; set; }
    public double Foobar2 { get; set; }
    public string BufferProperty { get; set; }
}

var obj = new Foo() {Bar1 = 2, Bar2 = 4, Foobar1 = 5, Foobar2 = 6};
var obj2 = new Foo() { Bar1 = 2, Bar2 = 4, Foobar1 = 5, Foobar2 = 7 };

var list = new List<Foo>();
list.Add(obj);  // 17
list.Add(obj2); // 18


Console.WriteLine(list.Sum(x => sumNumericalProperties(x))); // 35

Respondido el 04 de Septiembre de 13 a las 02:09

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.