tags:

views:

179

answers:

4

I need to calculate a whole bunch of averages on an List of Surveys. The surveys have lots of properties that are int and double valued. I am creating a business object to handle all the calculations (there are like 100) and I'd rather not code 100 different methods for finding the average for a particular property.

I'd like to be able to have the UI pass a string (representing the property) and have the the business object return an average for that property.

So, like...

int AverageHeightInInches = MyObject.GetIntAverage("HeightInInches"); . . . Then have linq code to calculate the result.

Thanks!

+1  A: 

Here is an example to do that.

        class Survey
        {
            public int P1 { get; set; }
        }

        class MyObject
        {
            readonly List<Survey> _listofSurveys = new List<Survey> { new Survey { P1 = 10 }, new Survey { P1 = 20 } };


            public int GetIntAverage(string propertyName)
            {
                 var type = typeof(Survey);
                var property = type.GetProperty(propertyName);
                return (int)_listofSurveys.Select(x => (int) property.GetValue(x,null)).Average();

            }
        }
        static void Main(string[] args)
        {
            var myObject = new MyObject();
            Console.WriteLine(myObject.GetIntAverage("P1"));
            Console.ReadKey();
        }
Darryl Braaten
This one is good too, but I'll need to test it out thoroughly tomorrow. Up one though.
Paul
+3  A: 

Hi there,

I have created this little example, it uses the System.Linq.Expression namespace to create a function that can calculate averages based on the property name. The function can be cached for later use, reflection is only used to create the function, not each time the function is executed.

EDIT: I removed the existing reflection example and updated the current example to show the ability to walk a list of properties.

static class Program
{
    static void Main()
    {
        var people = new List<Person>();

        for (var i = 0; i < 1000000; i++)
        {
            var person = new Person { Age = i };

            person.Details.Height = i;
            person.Details.Name = i.ToString();

            people.Add(person);
        }

        var averageAgeFunction = CreateIntegerAverageFunction<Person>("Age");
        var averageHeightFunction = CreateIntegerAverageFunction<Person>("Details.Height");
        var averageNameLengthFunction = CreateIntegerAverageFunction<Person>("Details.Name.Length");

        Console.WriteLine(averageAgeFunction(people));
        Console.WriteLine(averageHeightFunction(people));
        Console.WriteLine(averageNameLengthFunction(people));
    }

    public static Func<IEnumerable<T>, double> CreateIntegerAverageFunction<T>(string property)
    {
        var type = typeof(T);
        var properties = property.Split('.');   // Split the properties

        ParameterExpression parameterExpression = Expression.Parameter(typeof(T));
        Expression expression = parameterExpression;

        // Iterrate over the properties creating an expression that will get the property value
        for (int i = 0; i < properties.Length; i++)
        {
            var propertyInfo = type.GetProperty(properties[i]);
            expression = Expression.Property(expression, propertyInfo);  // Use the result from the previous expression as the instance to get the next property from

            type = propertyInfo.PropertyType;
        }

        // Ensure that the last property in the sequence is an integer
        if (type.Equals(typeof(int)))
        {
            var func = Expression.Lambda<Func<T, int>>(expression, parameterExpression).Compile();
            return c => c.Average(func);
        }

        throw new Exception();
    }
}

public class Person
{
    private readonly Detials _details = new Detials();

    public int Age { get; set; }
    public Detials Details { get { return _details; } }
}

public class Detials
{
    public int Height { get; set; }
    public string Name { get; set; }
}
Rohan West
I like this, but I'm gonna need to test it out before saying it is the answer...Voted one up though until tomorrow when I can test.
Paul
No problem, hope you can adapt it to fit your needs
Rohan West
Not sure if I should ask another questionOkay...Works but how about for extended properties...That is if Survey is composed of other objects and I want to get the average of one of those sub object's properties.Survey.ExpenseInformation.ManHoursPerYear <- that's an intLove to be able to do int AverageManHoursPerYear = MyObject.GetIntAverage("ExpenseInformation.ManHoursPerYear");
Paul
Hi there Paul, i have updated the example so now you can supply a list of properties, the final property must be an integer. The example isn't checking for null properties, for example Person.Details has to be initialized.
Rohan West
+1  A: 

You can do this without reflection (both int and double are supported):

public static double Average(this IEnumerable<Survey> surveys, Func<Survey, int> selector)
{
    return surveys.Average(selector);
}

public static double Average(this IEnumerable<Survey> surveys, Func<Survey, double> selector)
{
    return surveys.Average(selector);
}

Usage:

var average1 = surveys.Average(survey => survey.Property1);

var average2 = surveys.Average(survey => survey.Property2);
Bryan Watts
Not really what I was looking for...I knew that but I only have a string represeentation of the property name not an instance of the object (and therefore the actual property). The other answeres are spot on.
Paul
Ah, I thought you were just using string names because it seemed the most obvious. Since you had hard-coded them in the example, I assumed this solution would also suffice.
Bryan Watts
+1  A: 

if you are using linq2sql i would suggest DynamicLinq

you could then just do

   datacontext.Surveys.Average<double>("propertyName");

the dynamic linq project provides the string overloads to IQueryable.

luke