如何将类的属性作为方法的参数传递?

6
我有一个类,其中有十几个表示各种财务领域的属性。我还有另一个类需要单独对每个字段执行一些计算。这些计算方法内部的代码除了执行计算的字段不同之外都完全相同。
是否可以传递属性名称作为参数,并只使用一个方法来执行所有工作,而不是为每个属性编写12个方法?
此外,我确定可以通过反射实现这一点,但我在其他代码中看到过使用lambda完成相同工作的方式,并且想知道这是否是可以使用lambda的候选项。
如请求的那样,这里是一个示例:
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
}

我的目标是只创建一个名为GetTotalAmountByDate()的方法,并传入开始日期、结束日期以及需要访问的属性名称(例如: ReleasedFederalAmount 或 ReleasedLocalAmount等)。

我希望这准确地描述了我想要实现的内容。


如果您可以共享这样一个函数的示例以及类属性可能看起来像什么,回答将会更容易。 - Fredrik Mörk
2
我很好奇您的计算方法为什么不能将金融领域类的实例和一个值(即要执行计算的属性值)作为参数。您能否发布一些桩代码来阐明您的问题? - Ben M
在您的函数定义中使用与类属性相同的类型。 - Syed Tayyab Ali
请问您能否指定所有字段是否具有相同的类型,以及计算是否需要更新字段? - VoidPointer
是的,我同意,提供一段代码示例或者更好地解释功能会更好。听起来你应该只需对执行计算的函数进行抽象,并让这些包含数据的类继承它并自行完成工作。 - Zensar
4个回答

6
如果您的属性都是数字且可以被同质地视为单个类型(比如说 decimal),则不需要使用反射。像下面这样的代码就可以解决问题:
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;
}

您可以为所有特定情况提供适当命名的版本:
public decimal GetTotalReleasedFederalAmountByDate()
{
    return ComputeFinancialSum( BeginDate, EndDate, 
                                (x) => x.ReleasedFederalAmount );
}

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

// other versions ....

1
如果您的属性不是相同类型(比如说您需要使用int和long属性作为参数),该怎么办?
基于这个答案,您可以通过创建一个通用方法来改进它。 https://dev59.com/sEjSa4cB1Zd3GeqPGpUZ#1179210
protected decimal ComputeFinancialSum<T>( DateTime? beginDate, DateTime? endDate,
                                       Func<FinancialInfo,T> 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;
}

然后,您只需传递一个将不同类型的不同字段映射到函数的函数即可。

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

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

我已经使用答案中的原始代码来展示一种方法,但实际上我使用了类似于linq表达式的方式。

IEnumerable.Where(fieldToUse== null).OrderDescendingBy(fieldToUse)

根据任何属性类型对集合进行过滤和排序

0
除了Jon Skeet提出的基于lambda的好建议之外,你可以尝试像这样做。(当然,这可能会改变你的一些代码的工作方式。)
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);
    ...
  }
}

0

这可能是最低技术的答案,但为什么不使用 switch 并合并多个 "GetTotal...Amount" 函数呢?

 // 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;
    }

这样似乎更安全,因为所有逻辑都在您的控制下(没有人传递函数给您执行)。

如果您需要对不同数量执行不同操作,则可以进一步拆分:

将处理部分转换为函数:

      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;
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接