如何防止递归

时间:2011-05-18 21:55:45

标签: c# recursion

鉴于名为OrderInfo的类,确保其他开发人员(包括我自己)不会意外地产生此处显示的递归错误的最佳方法是什么:

public class OrderInfo : ICollection<UnitModule> {

  // Other code for class...

  public bool Changed { get; private set; }

  public void Save() {
    Save(this); // I want the static method to handle saving the data
  }

  public static void Save(OrderInfo item) {
    for (int i = 0; i < item.Count; i++) {
      if (item[i].Changed) {
        item[i].Save();
      }
    }
    if (item.Changed) {
      item.Save(); // this would be bad!
      // Instead, all of the other developers should have to call the
      // Database Save method.
      // Is there a way to ensure this happens or do I have to rely on
      // everyone remembering this?
    }
  }

}

编辑:使用标记的答案,我可以按如下方式编写我的课程(为什么无关紧要 - 这只会阻止递归):

public class OrderInfo : ICollection<UnitModule> {
  // Other code for class...
  bool saving; // <= new variable
  public bool Changed { get; private set; }

  public void Save() {
    if (!saving) {
      Save(this);
    } else {
      throw new Exception("This item is already being saved.");
    }
  }

  public static void Save(OrderInfo item) {
    item.saving = true;
    try {
      for (int i = 0; i < item.Count; i++) {
        if (item[i].Changed) {
          item[i].Save();
        }
      }
      if (item.Changed) {
        // item.Save(); <= NOTE: this would throw an exception
        DataAccess.Save(item);
        item.Changed = false;
      }
    } finally {
      item.saving = false;
    }
  }
}

6 个答案:

答案 0 :(得分:4)

通常情况下,超出堆栈限制时的崩溃会导致出现错误的警告。只要代码在投入生产之前得到了粗略的测试,就会有人抓住这个错误。

答案 1 :(得分:2)

这可能有效:

public static void Save(OrderInfo item) {
  for (int i = 0; i < item.Count; i++) {
    if (item[i].Changed) {
      item[i].Save();
    }
  }
  if (item.Changed) {
    item.Changed = false; // prevents recursion!
    item.Save();
    if error saving then item.Changed = true; // reset if there's an error
  }
}

答案 2 :(得分:2)

我不认为你的问题是不必要的递归。我认为你的问题是你已经实现了一个静态方法,它将类的一个实例作为唯一的参数。

为什么你甚至首先创建了一个静态方法?带有签名public void Foo.Bar(Foo f)任何方法的用例是什么?为什么要使用Foo.Bar(f)代替f.Bar()?我不知道这个问题可能有很好的答案。但这就是我先想到的。

答案 3 :(得分:1)

我会按优先顺序使用以下方法之一:

  • 重构代码以防止开发人员错误
  • 将一个静态分析工具(如FxCop)应用于检查此案例或类似案例的相当具体的规则
  • 添加某种if-check以防止通话
  • 如果运行时性能不是问题,请查看堆栈帧以找出调用的状态,并至少抛出异常。这虽然非常脆弱丑陋。

答案 4 :(得分:1)

这看起来像代码气味(如果某些东西可以“看起来”像“气味”)。我会考虑重构这个课程。为什么OrderInfo有一个公共静态方法,看起来可以用来保存其他OrderInfo个实例?

答案 5 :(得分:0)

可以这样做:

private bool saving;
public void Save() 
{
    if(this.saving)
    {
        throw new InvalidOperationException("accidental recursion");
    }
    try
    {
        this.saving = true;
        OrderInfo.Save(this); // I want the static method to handle saving the data
    }
    finally
    {
        this.saving = false;
    }
}

但正如其他人所说,你应该可能重构你的代码,以减少错误。