Gibbo
Gibbo

Reputation: 660

C# generic list with many types

I might have worded the title incorrectly but basically what I am wanting to do is have a list of the same object, that has different generic types within it.

The example being this, I have a Tower Defence game that works using a Tile based map. So I have layers (tile layer, object layer, ai layer). So I have this class:

 public class Layer<T>{

    // The name of the layer
    private String name;

    // All the cells in this layer
    private Cell<T>[] cells; 

    public Layer(Map map, String name){
        cells = new Cell<T>[map.Width * map.Height];
        this.name = name;
        CreateCells();
    }

    /// <summary>
    /// Fills the array of cells with blank, unoccupied cells
    /// </summary>
    private void CreateCells(){
        var index = 0;
        for (var x = 0; x < cells.Length; x++){
            for (var y = 0; y < cells.Length; y++){
                cells[index] = new Cell<T>(x, y);
                index++;
            }
        }
    }

    // Gets the array of cells in this layer
    public Cell<T>[] GetCells(){
        return cells;
    }

    /// <summary>
    /// Gets a cell at a specific index/coordinate
    /// </summary>
    /// <param name="x"></param>
    /// <param name="y"></param>
    /// <returns>The cell at the X and Y coordinate/index</returns>
    public Cell<T> GetCellAt(int x, int y){
        foreach (var c in cells){
            if (c.GetX() != x || c.GetY() != y) continue;
            return c;
        }
        throw new Exception("Cell not found at " + x+"x"+y);
    }

    /// <summary>
    /// Gets the name of this layer
    /// </summary>
    /// <returns></returns>
    public String GetName(){
        return name;
    }

}

Now this is fine, this means I can specify a layer that holds a type of object. In the tile layer I want cells that can hold tiles, in objects I want the layer to hold objects etc etc.

Problem is, when I try to create a list of layers in my map class. I am not quite sure on the syntax for a so called "wildcard" type thing like in java (?).

I tried this, to no success:

      private List<Layer<dynamic>> layers; 

It freaks out when I do this:

            layers = new List<Layer<dynamic>>();
        layers.Add(new Layer<Tile>(this, "tiles"));

So I am obviously using the incorrect syntax for this.

What should I do in this case?

UPDATE 1:

Following suggestions from people, I have tried using an interface for the common methods inside layer (get cell and what not). The problem with this design is that the layer requires type T in order to determine what type of object each cell can use.

UPDATE 2:

I can not figure out a way to do this any other way, so I decided to go with this solution:

Removed generics all together (almost) and instead of giving cell type T for occupant, I gave it type of GameObject. Now most things do inherit from game object (towers, tiles, obstacles, particles) however none of the AI related code does, so I need to unfortunately change my path finding code to do so.

I now have the following:

   public class Layer{

    // The name of the layer
    private String name;

    // All the cells in this layer
    private Cell[] cells; 

}

Generics have been removed from the Cell class as well:

    public class Cell{

    // The object inside this cell
    private GameObject occupant; // Note the new occupant type
    // The width and height of the cell in world coordinates
    public const float WIDTH = 1, HEIGHT = 1;
    // The coordinate of this cell
    private int x, y;
}

Now I am using a generic method inside the Cell class like so:

        /// <summary>
    /// Gets the occupant in this cell
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public T GetOccupant<T>() where T : GameObject{
        return occupant as T;
    }

So this code here, works as intended:

            layers = new List<Layer>();
        layers.Add(new Layer(this, "tiles"));
        GetLayer("tiles").GetCellAt(5, 5).SetOccupant(new EnemyScientist(null, 5, 1, 6));
        Enemy e = GetLayer("tiles").GetCellAt(5, 5).GetOccupant<Enemy>();

Although not the nicest looking code, the map system will only be used for this game so I am not particularly worried about writing re-usable code, mostly functional code. Ignore the fact that I have just stuck an enemy in the tile layer but I will add some sort of cheap check (typeof) to make sure I don't mess that up somewhere.

Upvotes: 2

Views: 1775

Answers (2)

M.G.E
M.G.E

Reputation: 371

In .NET Framework library it is implemented like this:

class Program
{
    static void Main(string[] args)
    {
        var layers = new List<ILayer<object>>();
        layers.Add(new Layer<string>()); // It is covariance so we can only use reference types.
    }
}

public interface ILayer<out T>
{
    void CreateCells();
    ICell<T>[] GetCells();
}

public class Layer<T> : ILayer<T>
{ }

public interface ICell<out T>
{ }

public class Cell<T> : ICell<T>
{ }

Upvotes: 0

JLRishe
JLRishe

Reputation: 101680

The problem is that Layer<Tile> and Layer<Ai> don't have any parent class in common that you could cast both to. The closest is the very general object that all reference types inherit from.

A common approach in this situation is, as Lasse suggests, define a common interface that they all inherit from:

interface ILayer 
{
    void CreateCells();
}

public class Layer<T> : ILayer 
{

Then your map can have a List of ILayers:

layers = new List<ILayer>();
layers.Add(new Layer<Tile>(this, "Tiles"));

You'll need to think of a way to design your ILayer interface in such a way that it is not dependent on the generic parameter T.

Upvotes: 3

Related Questions