I have a class, represented below as Example class. In my DoSomeLogic Method, I need to sum a collection of Example class, and want to return a new instance of Example class where each numerical property contains the sum of that property from the collection.
Below is an example of how i would typically do this, but my problem is that I am continuously updating my example class, adding new properties etc. When doing this I need to remember to also update the sum method for example class too, or my code is broken, which seems like a hurdle/vulnerability I'd like to avoid.
Can anyone show me a better way to do this generically, so i don't need to update the sum method if i add a new numerical property to the example class?
public class Example
{
public Example()
{
}
public Example(int a, int b, float c)
{
A = a;
B = b;
C = c;
IgnoreThis = "Non numerical properties should be ignored by the sum method";
}
public int A { get; set; }
public int B { get; set; }
public float C { get; set; }
public string IgnoreThis {get; set;}
}
using System.Collections.Generic;
using System.Linq;
public static class Extension
{
public static Example Sum(this IEnumerable<Example> source)
{
Example result = new Example();
result.A = source.Sum(x => x.A);
result.B = source.Sum(x => x.B);
result.C = source.Sum(x => x.C);
return result;
}
}
using System.Collections.Generic;
public class Usage
{
private List<Example> Examples = new List<Example>();
public void DoSomeLogic()
{
Examples.Add(new Example(1, 2, 3.75f));
Examples.Add(new Example(2, 3, 6.25f));
Example SumOfEachIndividualProperty = Examples.Sum();
//Expected result from logging SumOfEachIndividualProperty.A is 3;
//Expected result from logging SumOfEachIndividualProperty.B is 5;
//Expected result from logging SumOfEachIndividualProperty.C is 10;
}
}
You could solve this using reflection, but I think that's overkill (and would not be very performant).
You could consider putting the "add" functionality into the class itself:
public class Example
{
public Example()
{
}
public Example(int a, int b, float c)
{
A = a;
B = b;
C = c;
IgnoreThis = "Non numerical properties should be ignored by the sum method";
}
public void Add(Example other)
{
A += other.A;
B += other.B;
C += other.C;
}
public int A { get; set; }
public int B { get; set; }
public float C { get; set; }
public string IgnoreThis { get; set; }
}
Then your extension method would become:
public static Example Sum(this IEnumerable<Example> source)
{
var result = new Example();
foreach (var example in source)
{
result.Add(example);
}
return result;
}
When you add new members to the Example
class you would need to update the Add()
method accordingly, but you would not need to modify the extension method.
ADDENDUM: Since someone was wondering about using INumber
in the implementation that uses reflection, I thought I'd have a go.
Firstly, here's a example class that you want to sum the properties for:
public class Example
{
public Example()
{
}
public Example(int a, int b, float c)
{
A = a;
B = b;
C = c;
IgnoreThis = "Non numerical properties should be ignored by the sum method";
}
public override string ToString()
{
return $"A={A}, B={B}, C={C}";
}
public int A { get; set; }
public int B { get; set; }
public float C { get; set; }
public string IgnoreThis { get; set; } = "";
}
Here's a sample program that demonstrates how we want to add up the properties for all the items in a collection. Note the expected output:
public static class Program
{
public static void Main()
{
var items = new[]
{
new Example(1, 4, 7),
new Example(2, 5, 8),
new Example(3, 6, 9)
};
var totals = Extension.SumNumericProperties(items);
Console.WriteLine(totals); // A=6, B=15, C=24
}
}
And here's how I implemented the Extension
class:
If you need to add additional numeric types (e.g. decimal
) you'd just need to add an new numericProperties
item with the required type and add calls to sumValueTo()
and assignResults()
.
public static class Extension
{
public static T SumNumericProperties<T>(IEnumerable<T> items) where T: new()
{
var intAdders = numericProperties<int, T>();
var floatAdders = numericProperties<float, T>();
var shortAdders = numericProperties<short, T>();
var doubleAdders = numericProperties<double, T>();
foreach (var item in items)
{
sumValueTo(intAdders, item);
sumValueTo(floatAdders, item);
sumValueTo(shortAdders, item);
sumValueTo(doubleAdders, item);
}
T result = new();
assignResults(intAdders, result);
assignResults(floatAdders, result);
assignResults(shortAdders, result);
assignResults(doubleAdders, result);
return result;
}
static void sumValueTo<TNumber, T>(List<NumericPropertyAdder<TNumber>> numericProperties, T item) where TNumber : INumber<TNumber>
{
foreach (var numericProperty in numericProperties)
{
numericProperty.Add(item!);
}
}
static void assignResults<TNumber, T>(List<NumericPropertyAdder<TNumber>> numericProperties, T item) where TNumber : INumber<TNumber>
{
foreach (var numericProperty in numericProperties)
{
numericProperty.AssignResult(item!);
}
}
static List<NumericPropertyAdder<TNumber>> numericProperties<TNumber, TOwner>() where TNumber : INumber<TNumber>
{
return (
from prop in typeof(TOwner).GetProperties()
where prop.PropertyType.IsAssignableTo(typeof(INumber<TNumber>))
select new NumericPropertyAdder<TNumber>(prop)
).ToList();
}
}
public sealed class NumericPropertyAdder<T> where T: INumber<T>
{
public NumericPropertyAdder(PropertyInfo property)
{
_property = property;
}
public void Add(object propertyHolder)
{
var value = (T) _property.GetValue(propertyHolder)!;
_sum += value;
}
public T Sum()
{
return _sum;
}
public void AssignResult(object propertyHolder)
{
_property.SetValue(propertyHolder, _sum);
}
T _sum = T.AdditiveIdentity;
readonly PropertyInfo _property;
}
My conclusion is that the added complexity of handling the generic types like this might not actually be worth it... ;)