System.StackOverflowException error
本文关键字:error StackOverflowException System | 更新日期: 2023-09-27 18:29:22
我正在尝试创建一个2D洞穴生成系统。当我运行程序时,在我尝试从它自己的类创建新对象后,我会得到"System.StackOverflowException"异常。
我的洞穴发生器是这样工作的:
我创建了一个包含不同类型细胞(如墙、水或空白空间)的ID(整数)的映射。
首先,我的"Map"类创建了一个充满墙壁的地图,然后在地图的中心创建了一种"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;
}
}
虽然您可以通过在堆栈上创建太多非常大的对象来获得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)
这就留下了你剩下的三个功能
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
这三个函数形成了一个调用循环,因此,如果函数中的分支逻辑不正确,可能会导致非终止循环,从而导致堆栈溢出。
因此,看看函数中的分支逻辑
矿工
构造函数只有一个分支,这取决于状态是否为"Active"
。它总是活动的,因为这是创建对象的方式,所以构造函数总是调用StartDigging
。这感觉就像是状态没有得到正确处理,尽管你将来可能会把它用于其他事情。。。
顺便说一句,通常认为做大量处理是不好的做法,而不需要在对象构造函数中创建对象。所有的处理都发生在构造函数中,这感觉是错误的。
TryToCreateNewMiner
这有一个分支,8%的时间,它会创建一个新的矿工并调用构造函数。因此,每调用10次TryToCreateNewMiner
,我们就很有可能至少成功一次。新矿工最初在与父对象相同的位置启动(x和y不变)。
开始挖掘
这个方法中有相当多的分支。我们感兴趣的主要方面是关于调用TryToCreateNewMiner
的条件。让我们看看分支:
if(state=="Active")
这目前是一个冗余检查(它始终处于活动状态)。
while (!needStop && !IsOutOfBounds(x, y)) {
该终止条款的第一部分从未被触发。needStop只设置为真正的if(!IsLastMiner)
。由于minersCount
总是1,所以它总是最后一个矿工,所以needStop
永远不会被触发。您使用minersCount
的方式表明,您认为它是在Miner
的实例之间共享的,但事实并非如此。如果这是你的意图,你可能想了解static
变量。
termination子句的第二部分是退出循环的唯一途径,如果x或y到达映射的边缘,就会触发它。
while(dir==0)
这是一个毫无意义的检查,dir
只能是1到5之间的数字,因为这是ChooseDirection
返回的数字。
if(!AroundIsNothing())
这是在检查矿工可以移动到的位置是否都设置为0。如果不是,则调用GetIDFromDirection。这是关键。如果Miner当前被0包围,则不会设置ID
,它将保持在以前的值。在刚刚创建矿工的情况下,这将是-1
(我们知道这可能发生,因为所有矿工都是在矿工创建它的位置创建的)。
最后两个检查if(ID==1)
和if(ID==0 && IsLastMiner())
保护移动Miner的代码(通过调用dig或move)。因此,如果ID不是0,或者此时不是1,矿工将不会移动。这可能会导致问题,因为它就在调用TryToCreateNewMiner
之前,所以如果程序遇到这种情况,它将陷入一个循环,矿工不会移动,它不断试图在同一位置创建新的矿工。8%的时间,这将起作用,在相同的位置创建一个新的矿工,它将执行相同的检查并进入相同的循环,再次不移动并尝试创建一个新矿矿机,直到堆栈空间不足,程序崩溃。
你需要看看你的终止条款和你处理ID
的方式,如果Miner完全被0包围,你可能不希望它停止做任何事情。