简体   繁体   中英

C# Singleton stackoverflow

I'm trying to make a game in which I use a statemachine. The "GameState" creates the world (on entering) by calling getInstance() from the "World" class which is a singleton.

The World singleton has a 2D List with "Tile" objects (the world is made out of tiles). In the constructor of the World class the list is filled by a nested for loop which uses the SimpleTileFactory class to create tiles and puts them in the list.

Problem is that I get a StackOverflow. I debugged the code and found that the constructor of the World singleton is called more than once, which is probably causing the stack overflow. I can't find out why it's called more than once though and have the feeling I'm overlooking something simple. Is my singleton correct?

The Code is below.

Thanks in advance.

GameState Class

public partial class GameState : UserControl, IState<MainView>
{
    private bool ready = false;

    public GameState()
    {
        InitializeComponent();
    }

    public void enter(MainView owner)
    {
        this.Width = owner.Width;
        this.Height = owner.Height;
        this.Location = new Point(0, 0);
        owner.Controls.Add(this);
        World.getInstance();

        this.ready = true;
    }

    public void update(MainView owner)
    {
        this.Refresh();
    }

    public void exit(MainView owner)
    {
        owner.Controls.Remove(this);
    }

    public bool isReady()
    {
        return this.ready;
    }

    private void GameState_Paint(object sender, PaintEventArgs e)
    {
        //for (int i = 0; i < World.getInstance().tiles.Count; i++)
        //{
        //    for (int j = 0; j < World.getInstance().tiles[0].Count; j++)
        //    {
        //        //e.Graphics.DrawImage(new Image(), new Rectangle());
        //    }
        //}
    }
}

World Class

class World
{
    private static World instance;
    public GameResources gameResources;

    public SimpleTileFactory tileFactory;

    public List<List<Tile>> tiles = new List<List<Tile>>();
    public Size worldSize = new Size(100, 100);

    private World()
    {
        this.gameResources = new GameResources();
        this.tileFactory = new SimpleTileFactory();

        for (int i = 0; i < worldSize.Height; i++)
        {
            List<Tile> row = new List<Tile>();
            for (int j = 0; j < worldSize.Width; j++)
            {
                row.Add(tileFactory.createTile(new Point(j, i)));
            }
        }
    }


    public static World getInstance()
    {
        if (instance == null)
        {
            instance = new World();
        }

        return instance;
    }
}

SimpleTileFactory Class

class SimpleTileFactory
{
    public SimpleResourceFactory resourceFactory = new SimpleResourceFactory();
    private Random random = new Random();

    private int maxNumOfResourcesPerTile = 5;

    public SimpleTileFactory()
    {

    }


    public Tile createTile(Point location)
    {
        //create tile
        Tile tile = new Tile();
        tile.location = location;

        //give tile terrain type
        Terrain terrain = new Terrain();
        terrain.type = random.Next(0, World.getInstance().gameResources.terrainNames.Count);
        terrain.name = World.getInstance().gameResources.terrainNames[terrain.type];
        terrain.accessability = 1000;

        //add resources to terrain
        int numberOfResources = random.Next(0, maxNumOfResourcesPerTile + 1);

        for (int i = 0; i < numberOfResources; i++)
        {
            terrain.resources.Add(resourceFactory.createTerrainResource(terrain.type));
        }

        tile.terrain = terrain;

        return tile;
    }
}

SimpleResourceFactory Class

class SimpleResourceFactory
{
    public List<string> resourceNames = new List<string>();
    public List<Image> resourceImages = new List<Image>();
    private Random random = new Random();

    public SimpleResourceFactory()
    {
        resourceNames.Add("asdfasfd");
        resourceNames.Add("asdfasfd");
        resourceNames.Add("asdfasfd");
    }


    public TerrainResource createTerrainResource(int terrainType)
    {
        TerrainResource resource = new TerrainResource();
        resource.type = random.Next(0, resourceNames.Count);
        resource.name = this.resourceNames[resource.type];
        resource.amount = 100;

        return resource;
    }
}

Tile Class

class Tile
{
    public Point location;
    public Terrain terrain;

    public Tile()
    {

    }
}

Terrain Class

class Terrain
{
    public int type;
    public string name;
    public int accessability;
    public List<TerrainResource> resources = new List<TerrainResource>();

    public Terrain()
    {

    }
}

TerrainResource Class

class TerrainResource
{
    public int type;
    public string name;
    public int amount;

    public TerrainResource()
    {

    }
}

createTile is calling getInstance, and causing the overflow:

WpfApplication2.exe!WpfApplication2.SimpleResourceFactory.SimpleResourceFactory() Line 116 C# WpfApplication2.exe!WpfApplication2.SimpleTileFactory.SimpleTileFactory() Line 76 C# WpfApplication2.exe!WpfApplication2.World.World() Line 49 C# WpfApplication2.exe!WpfApplication2.World.getInstance() Line 66 C# WpfApplication2.exe!WpfApplication2.SimpleTileFactory.createTile(System.Windows.Point location) Line 95 C# WpfApplication2.exe!WpfApplication2.World.World() Line 56 C# WpfApplication2.exe!WpfApplication2.World.getInstance() Line 66 C# WpfApplication2.exe!WpfApplication2.SimpleTileFactory.createTile(System.Windows.Point location) Line 95 C# WpfApplication2.exe!WpfApplication2.World.World() Line 56 C# WpfApplication2.exe!WpfApplication2.World.getInstance() Line 66 C# WpfApplication2.exe!WpfApplication2.SimpleTileFactory.createTile(System.Windows.Point location) Line 95 C#

The problem was that createTile() was called within the World constructor. This way the World object isn't fully created yet when createTile() is executed which will then call getInstance() which in it's turn creates a new World (because it wasn't fully created yet) which calls createTile() again. It's an infinite loop.

I did a quick fix to check it by changing the World constructor and createTile() code as can be seen below. This is not the most beautiful way to solve it, so I'll probably just seperate GameResources from World.

    private World()
    {
        this.gameResources = new GameResources();
        this.tileFactory = new SimpleTileFactory();

        for (int i = 0; i < this.worldSize.Height; i++)
        {
            List<Tile> row = new List<Tile>();
            for (int j = 0; j < this.worldSize.Width; j++)
            {
                row.Add(tileFactory.createTile(new Point(j, i), this));
            }
            this.tiles.Add(row);
        }
    }

And the createTile method:

    public Tile createTile(Point location, World world)
    {
        //create tile
        Tile tile = new Tile();
        tile.location = location;

        //give tile terrain type
        Terrain terrain = new Terrain();
        terrain.type = random.Next(0, world.gameResources.terrainNames.Count);
        terrain.name = world.gameResources.terrainNames[terrain.type];
        terrain.accessability = 1000;

        //add resources to terrain
        int numberOfResources = random.Next(0, maxNumOfResourcesPerTile + 1);

        for (int i = 0; i < numberOfResources; i++)
        {
            terrain.resources.Add(resourceFactory.createTerrainResource(terrain.type));
        }

        tile.terrain = terrain;

        return tile;
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM