System.StackOverflowException错误

Ale*_*lex 1 c# stack-overflow exception system

我正在尝试创建一个2D洞穴生成系统.当我运行程序时,我尝试从自己的类创建新对象后得到"System.StackOverflowException"异常.

我的洞穴发生器的工作原理如下

我创建了一个包含不同类型单元格(如墙,水或空白空间)的ID(整数)的地图.

首先,我的所有"Map"类都会创建一个填充墙的地图,然后在地图的中心创建一个"Miner"对象.矿工挖掘地图并制作洞穴.问题是我想创造更多的矿工.所以,我挖掘地图的Miner创造了另一个矿工.但是,当我这样做时,我得到一个"System.StackOverflowException"异常.

如何在程序中跟踪StackOverflow的原因.这是我的矿工代码:

Miner.cs

public class Miner
{
    Random rand = new Random();

    public string state { get; set; }
    public int x { get; set; }
    public int y { get; set; }
    public Map map { get; set; }
    public int minersCount;

    public Miner(Map map, string state, int x, int y)
    {
        this.map = map;
        this.state = state;
        this.x = x;
        this.y = y;
        minersCount++;

        if (state == "Active")
        {
            StartDigging();
        }
    }

    bool IsOutOfBounds(int x, int y)
    {
        if (x == 0 || y == 0)
        {
            return true;
        }
        else if (x > map.mapWidth - 2 || y > map.mapHeight - 2)
        {
            return true;
        }
        return false;
    }

    bool IsLastMiner()
    {
        if (minersCount == 1)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    public void StartDigging()
    {
        if (state == "Active")
        {
            int dir = 0;
            bool needStop = false;
            int ID = -1;

            while (!needStop && !IsOutOfBounds(x, y))
            {
                while (dir == 0)
                {
                    dir = ChooseDirection();
                }

                if (!AroundIsNothing())
                {
                    while (ID == -1)
                    {
                        ID = GetIDFromDirection(dir);
                    }
                }
                else
                {
                    if (!IsLastMiner())
                    {
                        needStop = true;
                    }
                }

                if (ID == 1)
                {
                    DigToDirection(dir);
                    dir = 0;
                }

                if (ID == 0 && IsLastMiner())
                {
                    MoveToDirection(dir);
                    dir = 0;
                }

                TryToCreateNewMiner();
            }

            if (needStop)
            {
                state = "Deactive";
            }
        }
    }

    public void TryToCreateNewMiner()
    {
        if (RandomPercent(8))
        {
            Miner newMiner = new Miner(map, "Active", x, y);
        }
        else
        {
            return;
        }
    }

    bool AroundIsNothing()
    {
        if (map.map[x + 1, y] == 0 && map.map[x, y + 1] == 0 &&
            map.map[x - 1, y] == 0 && map.map[x, y - 1] == 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    void MoveToDirection(int dir)
    {
        if (dir == 1)
        {
            x = x + 1;
        }
        else if (dir == 2)
        {
            y = y + 1;
        }
        else if (dir == 3)
        {
            x = x - 1;
        }
        else if (dir == 4)
        {
            y = y - 1;
        }
    }

    void DigToDirection(int dir)
    {
        if (dir == 1)
        {
            map.map[x + 1, y] = 0;
            x = x + 1;
        }
        else if (dir == 2)
        {
            map.map[x, y + 1] = 0;
            y = y + 1;
        }
        else if (dir == 3)
        {
            map.map[x - 1, y] = 0;
            x = x - 1;
        }
        else if (dir == 4)
        {
            map.map[x, y - 1] = 0;
            y = y - 1;
        }
    }

    int GetIDFromDirection(int dir)
    {
        if (dir == 1)
        {
            return map.map[x + 1, y];
        }
        else if (dir == 2)
        {
            return map.map[x, y + 1];
        }
        else if (dir == 3)
        {
            return map.map[x - 1, y];
        }
        else if (dir == 4)
        {
            return map.map[x, y - 1];
        }
        else
        {
            return -1;
        }
    }

    int ChooseDirection()
    {
        return rand.Next(1, 5);
    }

    bool RandomPercent(int percent)
    {
        if (percent >= rand.Next(1, 101))
        {
            return true;
        }
        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

for*_*rir 5

虽然您可以StackOverflowExceptions通过在堆栈上创建太多非常大的对象来实现,但通常会发生这种情况,因为您的代码已进入一遍又一遍地调用相同函数链的状态.因此,要在代码中跟踪原因,最好的起点是确定代码调用自身的位置.

您的代码由Miner类本身调用的几个函数组成,其中大部分都是微不足道的

琐碎的函数,不会在类中调用任何其他内容.虽然这些函数可能会导致触发问题的状态,但它们不是终端函数循环的一部分:

IsOutOfBounds(int x, int y)
bool IsLastMiner()
bool AroundIsNothing()
void MoveToDirection(int dir)
void DigToDirection(int dir)
int GetIDFromDirection(int dir)
int ChooseDirection()
bool RandomPercent(int percent)
Run Code Online (Sandbox Code Playgroud)

这留下了剩余的三个功能

public Miner(Map map, string state, int x, int y) // Called by TryToCreateNewMiner
public void StartDigging()                        // Called by constructor
                                                  // Contains main digging loop
public void TryToCreateNewMiner()                 // Called by StartDigging
Run Code Online (Sandbox Code Playgroud)

这三个函数构成一个调用循环,因此如果函数中的分支逻辑不正确,则可能导致非终止循环,从而导致堆栈溢出.

所以,看一下函数中的分支逻辑

矿工

构造函数只有一个分支,基于状态是否为"Active".它始终处于活动状态,因为这是始终创建对象的方式,因此构造函数将始终调用StartDigging.这感觉状态没有被正确处理,尽管你将来可能会将它用于别的东西......

另外,通常认为进行大量处理是不好的做法,不需要在对象构造函数中创建对象.所有处理都发生在构造函数中,感觉不对.

TryToCreateNewMiner

这有一个分支,8%的时间,它将创建一个新的矿工并调用构造函数.因此,每TryToCreateNewMiner调用10次,我们就有机会至少成功一次.新矿工最初在与父对象相同的位置启动(x和y不会更改).

StartDigging

这种方法有一些分支.我们感兴趣的主要部分是有关呼叫的条件TryToCreateNewMiner.让我们看看分支:

if(state=="Active")

这是目前的冗余检查(它始终处于活动状态).

while (!needStop && !IsOutOfBounds(x, y)) {

永远不会触发此终止子句的第一部分.needStop只能设置为true if(!IsLastMiner).由于minersCount始终为1,因此它始终是最后一个矿工,因此needStop永远不会被触发.您使用的方式minersCount表明您认为它是在实例之间共享Miner,而不是.如果这是你的意图,你可能想要阅读static变量.

终止子句的第二部分是循环的唯一方法,如果x或y到达地图的边缘,则触发.

while(dir==0)

这是一个毫无意义的检查,dir只能是1到5之间的数字,因为这是返回的数字ChooseDirection.

if(!AroundIsNothing())

这是检查Miner可以进入的位置是否都设置为0.如果不是,则调用GetIDFromDirection.这是关键.如果Miner当前被0包​​围,ID将不会被设置,它将保持其先前的值.在刚刚创建了Miner的情况下,这将是-1(我们知道这可能发生,因为所有Miners都是在Miner创建它的位置创建的).

最后两次检查if(ID==1)if(ID==0 && IsLastMiner())保护移动Miner的代码(通过调用dig或move).因此,如果ID不为0,或此时1,则Miner将不会移动.这可能会导致问题,因为它是在调用之前TryToCreateNewMiner,所以如果程序进入这种情况,它将陷入Miner不移动的循环中,并且它一直试图在同一位置创建新的Miners.8%的时间这将起作用,在相同的位置创建一个新的矿工,它将执行相同的检查并进入相同的循环,再次不移动并尝试创建一个新的矿工,所以直到堆栈用完为止空间和程序崩溃.

您需要查看终止条款和处理方式ID,如果它完全被0环绕,您可能不希望Miner停止做任何事情.