0

this is my first posted question, so when I'm missing something or did something wrongplease let me know. :)

I try to create a generic class which should be used to create grids. So I'm inheriting from that generic class and passing all the needed arguments. For some reason the compiler isn't able to resolve the inheritance of my different classes and I'm not sure what's the reason behind it, since I'm not really used to Generics inside Inheritance.

Error Message:

Error CS1503 Argument 1: cannot convert from 'GridXZ<BattleGridObject>' to 'GridXZ<GridXZObject>' BattleGridSystem.cs 7

So the compiler cannot convert BattleGridObject to GridXZObject. That's weird for me, since BattleGridObject is inheriting from GridXZObject. So what's the obvious point I'm missing here? As far as I know BattleGridObject should be casted/converted (?) into his base class GridXZObject without any problem.

BattleGridSystem.cs

public class BattleGridSystem : MonoBehaviour
{
    public BattleGrid Grid;

    private void Awake()
    {
        Grid = new BattleGrid(10, 10, 5, Vector3.zero, (grid, x, z) => new BattleGridObject(grid, x, z));
    }
}

BattleGrid.cs

public class BattleGrid : GridXZ<BattleGridObject>
{
    public BattleGrid(int width, int height, float cellSize, Vector3 originPosition, Func<GridXZ<BattleGridObject>, int, int, BattleGridObject> createGridObject) 
        : base(width, height, cellSize, originPosition, createGridObject)
    {

    }
}

BattleGridObject.cs

public class BattleGridObject : GridXZObject
{
    public BattleGridObject(GridXZ<GridXZObject> grid, int x, int z) : base(grid, x, z)
    {

    }
}

GridXZ.cs

public abstract class GridXZ<TGridXZObject> where TGridXZObject : GridXZObject
{
    public event EventHandler<OnGridXZObjectChangedEventArgs> OnGridObjectChanged;

    public readonly int Width;
    public readonly int Height;
    public readonly float CellSize;

    protected readonly Vector3 OriginPosition;
    protected readonly TGridXZObject[,] GridArray;

    public GridXZ(int width, int height, float cellSize, Vector3 originPosition, Func<GridXZ<TGridXZObject>, int, int, TGridXZObject> createGridObject)
    {
        Width = width;
        Height = height;
        CellSize = cellSize;

        OriginPosition = originPosition;
        GridArray = new TGridXZObject[width, height];

        for (var x = 0; x < GridArray.GetLength(0); x++)
        {
            for (var z = 0; z < GridArray.GetLength(1); z++)
            {
                GridArray[x, z] = createGridObject(this, x, z);
            }
        }
    }
}

GridXZObject.cs

public abstract class GridXZObject
{
    protected readonly GridXZ<GridXZObject> Grid;
    protected readonly int X;
    protected readonly int Z;

    public GridXZObject(GridXZ<GridXZObject> grid, int x, int z)
    {
        Grid = grid;
        X = x;
        Z = z;
    }

    public override string ToString()
    {
        return $"{X}, {Z}";
    }
}

OnGridXZObjectChangedEventArgs.cs

public class OnGridXZObjectChangedEventArgs : EventArgs
{
    public int X;
    public int Z;
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    `BattleGridObject` expects a `GridXZ`, but the argument to the lambda factory method is a `GridXZ`. These are not assignable types. – Jeremy Lakeman Sep 02 '21 at 06:44
  • I'll give it a try to change the types inside `BattleGridObject`, but it still confuses me, since `BattleGrid` is inheriting `GridXZ` and `BattleGridObject` is inheriting `GridXZObject`. Also I'm sure that would be causing another conversion error, when calling the base constrctur inside `BattleGridObject.` – TH3UNKN0WN Sep 02 '21 at 06:50
  • @JeremyLakeman Okay, so however I change the types around inside `BattleGridSystem`, `BattleGrid` and `BattleGridObject` I can't find a working solution. Could you maybe explain to me why these types are not assignable? It seems I'm missing some crucial information about generics and inheritance. – TH3UNKN0WN Sep 02 '21 at 07:06
  • "So the compiler cannot convert BattleGridObject to GridXZObject." - that's not what the compiler error says. It says it can't convert from `GridXZ` to `GridXZ`. That's not the same thing at all - in the same way that "a banana is a fruit" but "a bunch of bananas" is not "a fruit bowl". (You can't convert from `List` to `List` because you can add *any* fruit to a `List`, but you can't add an apple to a `List`.) – Jon Skeet Sep 02 '21 at 07:52
  • (The duplicate question I've used is specifically about an interface, but it's the same principle and the answers explain the issue in this case.) – Jon Skeet Sep 02 '21 at 07:54
  • 1
    See also: https://ericlippert.com/category/covariance-and-contravariance/ – Jon Skeet Sep 02 '21 at 07:54
  • Thanks for the linked answer and I'm sorry for not using the search function correctly before posting the question. Like I said I was thinking the conversion should be possible since `BattleGridObject` is inherting from `GridXZObject`. Luckly it's working when using an interface in-between. – TH3UNKN0WN Sep 02 '21 at 08:03

1 Answers1

0

Okay. The solution was to add two interfaces for the Grid and GridObject and use them inside the abstract classes. This way the compiler knows how to convert between the different classes and the intellisense shows the correct datatypes.

I'm still not sure why the initial solution isn't working, but this way it is.

BattleGridSystem.cs

public class BattleGridSystem : MonoBehaviour
{
    public BattleGrid Grid;

    private void Awake()
    {
        Grid = new BattleGrid(10, 10, 5, Vector3.zero, (grid, x, z) => new BattleGridObject(grid, x, z));
    }
}

BattleGrid.cs

public class BattleGrid : GridXZ<BattleGridObject>
{
    public BattleGrid(int width, int height, float cellSize, Vector3 originPosition, Func<IGridXZ<IGridXZObject>, int, int, BattleGridObject> createGridObject) 
        : base(width, height, cellSize, originPosition, createGridObject)
    {
    }
}

BatleGridObject.cs

public class BattleGridObject : GridXZObject
{
    public BattleGridObject(IGridXZ<IGridXZObject> grid, int x, int z) 
        : base(grid, x, z)
    {
    }
}

IGridXZ.cs

public interface IGridXZ<out TGridXZObject> where TGridXZObject : IGridXZObject
{

}

GridXZ.cs

public abstract class GridXZ<TGridXZObject> : IGridXZ<TGridXZObject> where TGridXZObject : GridXZObject
{
    public event EventHandler<OnGridXZObjectChangedEventArgs> OnGridObjectChanged;

    public readonly int Width;
    public readonly int Height;
    public readonly float CellSize;

    protected readonly Vector3 OriginPosition;
    protected readonly TGridXZObject[,] GridArray;

    public GridXZ(int width, int height, float cellSize, Vector3 originPosition, Func<IGridXZ<IGridXZObject>, int, int, TGridXZObject> createGridObject)
    {
        Width = width;
        Height = height;
        CellSize = cellSize;

        OriginPosition = originPosition;
        GridArray = new TGridXZObject[width, height];

        for (var x = 0; x < GridArray.GetLength(0); x++)
        {
            for (var z = 0; z < GridArray.GetLength(1); z++)
            {
                GridArray[x, z] = createGridObject(this, x, z);
            }
        }
    }
}

IGridObject.cs

public interface IGridXZObject
{

}

GridXZObject.cs

public abstract class GridXZObject : IGridXZObject
{
    protected readonly IGridXZ<IGridXZObject> Grid;
    protected readonly int X;
    protected readonly int Z;

    public GridXZObject(IGridXZ<IGridXZObject> grid, int x, int z)
    {
        Grid = grid;
        X = x;
        Z = z;
    }

    public override string ToString()
    {
        return $"{X}, {Z}";
    }
}