tags:

views:

205

answers:

3

I have a class that has a dozen or so properties that represent various financial fields. I have another class that needs to perform some calculations on each of those fields separately. The code inside those calculation methods are identical except for the field that it does the calculation on.

Is there a way that I can pass a property name as a parameter and just have one method that does all of the performing work instead of the 12 methods for each property?

Also, I'm sure this can be accomplished via reflection, but I've seen in other code where lambdas are used in this same kind of fashion and was wondering if this is a candidate where this can be used.

As requested, here is an example:

public class FinancialInfo
{
    public virtual DateTime AuditDate { get; set; }
    public virtual decimal ReleasedFederalAmount { get; set; }
    public virtual decimal ReleasedNonFederalAmount { get; set; }
    public virtual decimal ReleasedStateAmount { get; set; }
    public virtual decimal ReleasedLocalAmount { get; set; }
    public virtual decimal ReleasedPrivateAmount { get; set; }
    // more fields like this
}

public class FinancialLedger()
{
    public virtual DateTime? BeginDate { get; set; }
    public virtual DateTime? EndDate { get; set; }
    public virtual IList<FinancialInfo> Financials { get; set; } //not actual implementation, but you get the idea
    public decimal GetTotalReleasedFederalAmountByDate()
    {
        if (BeginDate == null && EndDate == null)
            return 0;
        decimal total = 0;
        foreach (var fi in Financials)
        {
            if (someCondition)
                if (someSubCondition)
                    total += fi.ReleasedFederalAmount;
            else if (someOtherCondition)
                if (someOtherSubCondition)
                    total += fi.ReleasedFederalAmount;
            else if (anotherCondigion)
                total += fi.ReleasedFederalAmount;
        }
        return total;
    }
    public decimal GetTotalReleasedNonFederalAmountByDate()
    {
        // same logic as above method, 
        // but it accesses fi.ReleasedNonFederalAmount;
    }
    // More methods the same as the previous, just accessing different
    // members of FinancialInfo
}

My goal is to just make one method called GetTotalAmountByDate() and pass in a begin date, and end date and the name of the property (ReleasedFederalAmount or ReleasedLocalAmount, etc.) it needs to access.

I hope this depicts accurately what I'm trying to accomplish.

Thanks!

A: 

Apart from the good lamba-based suggestion from Jon Skeet, you could try something like this. (Of course, it could change the way some of your code works.)

public class ValueHolder
{
  object Value;
}

public class Main
{
  private ValueHolder value1 = new ValueHolder();
  private ValueHolder value2 = new ValueHolder();

  public Value1 { get { return value1.Value; } set { value1.Value = value; } }
  public Value2 { get { return value2.Value; } set { value2.Value = value; } }

  public ValueHolder CalculateOne(ValueHolder holder ...)
  {
    // Whatever you need to calculate.
  }

  public CalculateBoth()
  {
    var answer1 = CalculateOne(value1);
    var answer2 = CalculateOne(value2);
    ...
  }
}
John Fisher
A: 

This is probably the lowest-tech answer here, but why not just use a switch and merge the multiple "GetTotal...Amount" functions?

 // define some enum for your callers to use
 public enum AmountTypeEnum {
     ReleasedFederal = 1
 ,   ReleasedLocal = 2
 }

 public decimal GetTotalAmountByDate(AmountTypeEnum type)
    {
        if (BeginDate == null && EndDate == null)
            return 0;
        decimal total = 0;
        foreach (var fi in Financials)
        {
            // declare a variable that will hold the amount:
            decimal amount = 0;

            // here's the switch:
            switch(type) {
                case AmountTypeEnum.ReleasedFederal: 
                     amount = fi.ReleasedFederalAmount; break;
                case AmountTypeEnum.ReleasedLocal:
                     amount = fi.ReleasedLocalAmount; break;
                default: break;
            }

            // continue with your processing:
            if (someCondition)
                if (someSubCondition)
                    total += amount;
            else if (someOtherCondition)
                if (someOtherSubCondition)
                    total += amount;
            else if (anotherCondigion)
                total += amount;
        }
        return total;
    }

This seems safer, because all your logic remains under your control (no one is passing you functions to execute).

This can be further broken down if you need to actually do different things with different amounts:

Take the processing part and turn it into a function:

      private decimal ProcessNormal(decimal amount) {
           decimal total = 0;

           // continue with your processing:
            if (someCondition)
                if (someSubCondition)
                    total += amount;
            else if (someOtherCondition)
                if (someOtherSubCondition)
                    total += amount;
            else if (anotherCondition)
                total += amount;
          return total;
     }

 public decimal GetTotalAmountByDate(AmountTypeEnum type)
    {
        if (BeginDate == null && EndDate == null)
            return 0;
        decimal total = 0;
        foreach (var fi in Financials)
        {
            // declare a variable that will hold the amount:
            decimal amount = 0;

            // here's the switch:
            switch(type) {
                case AmountTypeEnum.ReleasedFederal: 
                     amount = fi.ReleasedFederalAmount; 
                     total = ProcessNormal(amount);
                     break;
                case AmountTypeEnum.ReleasedLocal: 
                     amount = fi.ReleasedLocalAmount; 
                     total = ProcessNormal(amount);
                     break;
                case AmountTypeEnum.NonReleasedOtherAmount:
                     amount = fi.NonReleasedOtherAmount; 
                     total = ProcessSlightlyDifferently(amount);  // for argument's sake
                     break;
                default: break;
            }
        }
        return total;
    }
Jeff Meatball Yang
+3  A: 

You don't need reflection if your properties are all numeric and can be homogeneously treated as a single type - let's say a decimal.

Something like this should do the trick:

protected decimal ComputeFinancialSum( DateTime? beginDate, DateTime? endDate,
                                       Func<FinancialInfo,decimal> propertyToSum )
{
    if (beginDate == null && endDate == null)
        return 0;
    decimal total = 0;
    foreach (var fi in Financials)
    {
        if (someCondition)
            if (someSubCondition)
                total += propertyToSum(fi);
        else if (someOtherCondition)
            if (someOtherSubCondition)
                total += propertyToSum(fi);
        else if (anotherCondigion)
            total += propertyToSum(fi);
    }
    return total;
}

You can then provide appropriately named versions for all of your specific cases:

public decimal GetTotalReleasedFederalAmountByDate()
{
    return ComputeFinancialSum( BeginDate, EndDate, 
                                (x) => x.ReleasedFederalAmount );
}

public decimal GetTotalReleasedNonFederalAmountByDate()
{
    return ComputeFinancialSum( BeginDate, EndDate, 
                                (x) => x.ReleasedNonFederalAmount );
}

// other versions ....
LBushkin
This is precisely what I was looking for, thanks!
Chris Conway