递归调用方法时防止System.StackOverflowException

时间:2018-08-31 02:54:07

标签: c# forms

我是C#(或一般编码)的新手,我想这个问题确实很愚蠢和令人困惑(我知道我很难做到),但是请帮助我。

我正在尝试使用表单应用程序进行扫雷。我做了一个10 x 10的按钮,如果您单击它,它将显示出周围的地雷数量。如果存在地雷,则会显示“ F”(首字母为“ False”)。

有一个构造函数,其中包含按钮,x和y位置,周围块的列表,其周围的地雷数量以及指示是否有地雷的布尔值。

我试图做的是,当玩家单击一个周围没有地雷的方块时清除了列表中的8个周围的方块,如果该方块周围的方块也没有任何地雷,这些该块周围的块也将被清除。该方法使用foreach来显示并检查该区块周围的地雷数量。如果没有地雷,则将相同的方法应用于该块(递归调用该方法)。问题是我不断收到System.StackOverflowException

我以某种方式理解为什么会发生这种情况,但是我只是想不出另一种方式。

//scroll to the bottom for the method with the problem

private void Form1_Load(object sender, EventArgs e)
{
    Random random = new Random();
    Button[,] buttons = new Button[10, 10]
    {
        { r0c0, r0c1, r0c2, r0c3, r0c4, r0c5, r0c6, r0c7, r0c8, r0c9 },
        { r1c0, r1c1, r1c2, r1c3, r1c4, r1c5, r1c6, r1c7, r1c8, r1c9 },
        { r2c0, r2c1, r2c2, r2c3, r2c4, r2c5, r2c6, r2c7, r2c8, r2c9 },
        { r3c0, r3c1, r3c2, r3c3, r3c4, r3c5, r3c6, r3c7, r3c8, r3c9 },
        { r4c0, r4c1, r4c2, r4c3, r4c4, r4c5, r4c6, r4c7, r4c8, r4c9 },
        { r5c0, r5c1, r5c2, r5c3, r5c4, r5c5, r5c6, r5c7, r5c8, r5c9 },
        { r6c0, r6c1, r6c2, r6c3, r6c4, r6c5, r6c6, r6c7, r6c8, r6c9 },
        { r7c0, r7c1, r7c2, r7c3, r7c4, r7c5, r7c6, r7c7, r7c8, r7c9 },
        { r8c0, r8c1, r8c2, r8c3, r8c4, r8c5, r8c6, r8c7, r8c8, r8c9 },
        { r9c0, r9c1, r9c2, r9c3, r9c4, r9c5, r9c6, r9c7, r9c8, r9c9 }
    };

    Square[,] squares = new Square[10, 10];

    for (int i = 0, ii = 0, iii = 0; i < 100; i++, ii++)
    {
        if (ii == 10)
        {
            ii = 0;
            iii++;
        }
        squares[ii, iii] = new Square(i, buttons[ii, iii], ii, iii, 0, true);
    }

    List<int> randoms = new List<int>();
    for (int i = 0; i < 10; i++)
    {
        int ii = random.Next(100);
        if (!randoms.Contains(ii))
        {
            squares[ii % 10, ii / 10].setSafe(false);
        }
        else
        {
            i--;
        }
        randoms.Add(ii);
    }

    for (int i = 0; i < 10; i++)
    {
        for (int ii = 0; ii < 10; ii++)
        {
            for (int iii = -1; iii < 2; iii++)
            {
                for (int iiii = -1; iiii < 2; iiii++)
                {
                    try
                    {
                        if (squares[i + iii, ii + iiii].getSafe() == false)
                            squares[i, ii].addNumber();
                    }
                    catch (System.IndexOutOfRangeException)
                    {
                    }
                }
                //if (squares[i, ii].getSafe() == false) squares[i, ii].getButton().Text = squares[i, ii].getSafe().ToString();
                //else squares[i, ii].getButton().Text = squares[i, ii].getNumber().ToString();
            }
        }
    }

    for (int i = 0; i < 10; i++)
    {
        for (int ii = 0; ii < 10; ii++)
        {
            for (int iii = -1; iii < 2; iii++)
            {
                for (int iiii = -1; iiii < 2; iiii++)
                {
                    try
                    {
                        squares[i, ii].addList(squares[i + iii, ii + iiii]);
                    }
                    catch (System.IndexOutOfRangeException)
                    {
                    }
                }
            }
        }
    }
}

这是Square类:

public class Square
{
    int id;
    Button button;
    int x;
    int y;
    int number;
    bool safe;
    List<Square> list = new List<Square>();

    public Square(int id, Button button, int x, int y, int number, bool safe)
    {
        this.id = id;
        this.button = button;
        this.x = x;
        this.y = y;
        this.number = number;
        this.safe = safe;

        button.Text = "";

        button.Click += button_Click;
    }

    public int getId()
    {
        return id;
    }

    public void setId(int i)
    {
        id = i;
    }

    public Button getButton()
    {
        return button;
    }

    public void setButton(Button b)
    {
        button = b;
    }

    public int getX()
    {
        return x;
    }

    public void setX(int i)
    {
        x = i;
    }

    public int getY()
    {
        return y;
    }

    public void setY(int i)
    {
        y = i;
    }

    public int getNumber()
    {
        return number;
    }

    public void setNumber(int i)
    {
        number = i;
    }

    public void addNumber()
    {
        number++;
    }

    public bool getSafe()
    {
        return safe;
    }

    public void setSafe(bool b)
    {
        safe = b;
    }

    private void button_Click(object sender, EventArgs e)
    {
        if (getSafe() == false) button.Text = getSafe().ToString();
        else button.Text = getNumber().ToString();
        if (getNumber() == 0) zeroReveal();
    }

//---------------------------------------------------
// this is the method that reveals surrounding blocks
//---------------------------------------------------

    private void zeroReveal()
    {
        foreach (Square s in list)
        {
            //revealing the blocks
            s.getButton().Text = s.getNumber().ToString();
            //call the same method if there's no mine
            //this is the line that keeps giving me exception
            if (s.getNumber() == 0) s.zeroReveal();
        }
    }

//-----------------------------------------------------

    public List<Square> getList()
    {
        return list;
    }

    public void setList(List<Square> sl)
    {
        list = sl;
    }

    public void addList(Square s)
    {
        list.Add(s);
    }
}

2 个答案:

答案 0 :(得分:3)

  

我是C#(或一般编码)的新手,我想这个问题确实很愚蠢和令人困惑(我知道我很难做到)

本主题使许多新开发人员感到困惑;不要为此烦恼!

  

如果没有地雷,则将相同的方法应用于该块(递归调用该方法)。

递归方法可能会造成混淆,但是如果使用标准模式进行设计,则可以避免SO异常。您尚未使用标准模式进行设计。

成功的递归方法的标准模式是:

  • 我不需要递归吗?
  • 如果是,请进行必要的计算以产生所需的效果并返回。现在问题已解决。
  • 如果否,那么我们要递归。
  • 将当前问题分解为一些较小问题。
  • 通过递归解决每个较小的问题。
  • 合并较小问题的解决方案以解决当前问题。
  • 问题现在已解决,请返回。

设计递归方法的最重要的事情是,每次递归都必须解决一个较小的问题,并且较小问题的序列必须从底部解决一种不需要递归的情况。如果不满足这两个条件,那么您将会出现堆栈溢出。

内部化该模式,并每次编写一个递归方法,然后将其实际写出:

int Frob(int blah)
{
   if (I am in the base case)
   {
     solve the base case
     return the result
   }
   else
   {
     find smaller problems
     solve them
     combine their solutions
     return the result
   }
 }

使用您的真实代码填充该模板,您将更有可能避免堆栈溢出。我已经写了数十年的递归方法,但我仍然遵循这种模式。

现在,在您的示例中,不需要递归的情况是什么?必须有一个,所以记下它是什么。接下来,您将如何保证递归解决了一个较小的问题?这通常是艰难的一步!考虑一下。

答案 1 :(得分:0)

由于zeroReveal永远递归调用自身而发生了堆栈溢出。要解决此问题,我们需要找到不需要它的方式来进一步对其自身进行调用。

方法的名称为我们提供了一个线索。如果该正方形已经被显示出来,那么该方法肯定不需要做任何事情,因为它已经被显示出来了。

如果按钮的Text属性尚未显示,则它看起来像是一个空字符串。因此,请更改foreach,以使其不会处理已经显示的正方形:

foreach (Square s in list)
{
    if (s.getButton().Text == ""))
    {
        // existing code in the foreach loop goes here
    }
}