2

I have a datatype PlayerStats which contains a lot of different data members. I want to calculate a score which is different for each data member (the case below looks at statistics.nrOfGoals).

private double getScore()
{
    double strength = 0;
    foreach (PlayerStats statistics in this.statistics)
    {
        double dateDiff = Math.Abs(nowDate.Subtract(statistics.date).Days / (365.25 / 12));
        dateDiff = Math.Pow(dateDiff, Form1.historyFactor);

        strength += (statistics.nrOfGoals * ValueTable.PointsPerGoals   ) / dateDiff;
    }

    return strength;
}

How can I make this function general and accept which datamember to look at instead of creating a lot of similar looking functions?

Something like

private double getScore(Type type, Type type2)
{
    double strength = 0;
    foreach (PlayerStats statistics in this.statistics)
    {
        double dateDiff = Math.Abs(nowDate.Subtract(statistics.date).Days / (365.25 / 12));
        dateDiff = Math.Pow(dateDiff, Form1.historyFactor);

        strength += (statistics.type * ValueTable.type2) / dateDiff;
    }

    return strength;
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194

3 Answers3

4

You can give a function as a parameter with signature PlayerStats -> Double:

private double getScore(Func<PlayerStats,double> type, double type2)
{
    double strength = 0;
    foreach (PlayerStats statistics in this.statistics)
    {
        double dateDiff = Math.Abs(nowDate.Subtract(statistics.date).Days / (365.25 / 12));
        dateDiff = Math.Pow(dateDiff, Form1.historyFactor);

        strength += (type(statistics) * type2) / dateDiff;
    }

    return strength;
}

And then call it with:

getScore(x => x.nrOfGoals,ValueTable.PointsPerGoals);

x => x.nrOfGoals is a lambda-expression that defines some kind of function that (in this case) takes as input a PlayerStats instance and returns a double.

In the code, you can then see type as a "function"/"method" and call it with type(y) (with y a PlayerStats instance).

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • 1
    @Oday: is there a specific reason to make `type2` a function as well? – Willem Van Onsem Jan 21 '15 at 14:05
  • In the first function, the second operand is ValueTable.PointsPerGoals. In the second function, the second operand is ValueTable.type2. I can infer that both multiplication operands can be changed. – Oday Fraiwan Jan 21 '15 at 14:10
  • @Oday: no: the first function takes only one parameter: the `PlayerStats`, the `double` in the signature is the type of the result. A `Func` is probably only useful if the result can change (for instance a random number generator). Here it is reasonable to assume the weight will remain fixed... – Willem Van Onsem Jan 21 '15 at 14:16
1

You could put the property names as string parameters and look up properties by name using reflection.

Attila Szasz
  • 3,033
  • 3
  • 25
  • 39
  • 1
    That will work, but it is more inefficient and less safe: using lambda-expressions, the compiler can check whether the aspect actually exists. Whereas reflection uses strings. If at one point you rename a property it will break. I think one better doesn't use reflection unless it is absolutely useful.. – Willem Van Onsem Jan 21 '15 at 14:06
  • I agree, but giving up type safety gives you maximum flexibility in naming any existing or future property to use in the statistics. – Attila Szasz Jan 21 '15 at 14:13
1

You can pass Func<PlayerStats, double> to your function, like:

private double getScore(Func<PlayerStats, double> evaluator)
{
    double strength = 0;
    foreach (PlayerStats statistics in this.statistics)
    {
        double dateDiff = Math.Abs(nowDate.Subtract(statistics.date).Days / (365.25 / 12));
        dateDiff = Math.Pow(dateDiff, Form1.historyFactor);

        strength += evaluator(statistics) / dateDiff;
    }

    return strength;
}

And then call it like (in your shown case)

getScore(x => x.nrOfGoals * ValueTable.PointsPerGoals);
Andrey Korneyev
  • 26,353
  • 15
  • 70
  • 71