【问题标题】:How to prevent recursion如何防止递归
【发布时间】:2011-05-18 21:55:44
【问题描述】:

鉴于名为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;
    }
  }
}

【问题讨论】:

  • 你有什么特别的理由让它成为静态成员?
  • 指向answers here会是陈词滥调吗?
  • @Marc Gravell - 即将发表完全相同的评论!
  • 发生了什么?我的问题发了两次吗?这在我之前发生过几次。是我的鼠标手指太抽搐还是谷歌浏览器出现故障?

标签: c# recursion


【解决方案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
      }
    }
    

    【讨论】:

    • 我得到了这个版本来实际做我想做的事。谢谢!
    【解决方案3】:

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

    你为什么一开始就创建了一个静态方法?带有签名public void Foo.Bar(Foo f)any 方法的用例是什么?你为什么要使用Foo.Bar(f) 而不是f.Bar()?这个问题可能有很好的答案,我不知道。但这是我首先要弄清楚的。

    【讨论】:

    • 目标是让 Save 类成员创建一个调用静态方法的线程。传递给静态方法的特定参数需要更改为通用对象,但这没有什么影响。
    • 我不得不承认我仍然不明白你为什么要这样做。
    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

        可以这样做:

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

        但正如其他人所说,您应该可能会重构您的代码以减少出错的可能性。

        【讨论】:

          猜你喜欢
          • 2018-09-20
          • 2017-09-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-01-03
          • 1970-01-01
          • 1970-01-01
          • 2017-01-07
          相关资源
          最近更新 更多