Search code examples
c#genericsinterfacecontravariance

Cannot convert from class to generic interface


EDIT: Updated to include actual code.

I am having an issue with some custom generic interfaces and I am not entirely sure what to do. The error I'm getting is:

Cannot convert from Map to IMap<ICell>

That error pops up when I try to pass Map as a parameter to a method that accepts IMap<ICell>. I have pasted sample code below. Just to be clear, FieldOfView doesn't use anything that hasn't been defined in ICell or IMap.

public class Map : IMap<Cell>
{
    private FieldOfView _fieldOfView;
    public int Width { get; }
    public int Height { get; }
    public Map(int width, int height)
    {
        Width = width;
        Height = height;
        _fieldOfView = new FieldOfView(this as IMap<ICell>);
        _fieldOfView = new FieldOfView((IMap<ICell>)this);
    }
    public IEnumerable<Cell> GetAllCells()
    {
        for (int x = 0; x < Width; x++)
        {
            for (int y = 0; y < Height; y++)
            {
                yield return GetCell(x, y);
            }
        }
    }
    public Cell GetCell(int x, int y)
    {
        return new Cell(x, y);
    }
    public void Copy(IMap<Cell> sourceMap)
    {
        // ...
    }
    public override string ToString()
    {
        var sb = new StringBuilder();
        foreach (var cell in GetAllCells())
        {
            sb.Append(cell.ToString());
        }
        return sb.ToString();
    }
}

public interface IMap<T> where T : ICell
{
    int Width { get; }
    int Height { get; }
    IEnumerable<T> GetAllCells();
    T GetCell(int x, int y);
    void Copy(IMap<T> sourceMap);
}

public class Cell : ICell
{
    public int X { get; }
    public int Y { get; }
    public Cell(int x, int y)
    {
        X = x;
        Y = Y;
    }
    public override string ToString()
    {
        return "overloaded";
    }
}

public interface ICell
{
    int X { get; }
    int Y { get; }
}

public class FieldOfView
{
    private readonly IMap<ICell> _map;
    public FieldOfView(IMap<ICell> map)
    {
        _map = map;
    }
    public void DoStuff()
    {
        foreach (var cell in _map.GetAllCells())
        {
            // ...
        }
    }
}

This is similar to this stack overflow question, but a little different. I tried implementing an interface IMap as well as IMap<T> : IMap where T : ICell, but am having issues with that as well.

Lastly, I'm not sure if this is solvable with co/contravariance, but I am using C#3.0 so that is out of the picture for me (unless switching versions is the only way).


I think it would be fine with an implicit / direct cast?

_fieldOfView = new FieldOfView(this as IMap<ICell>); // or
_fieldOfView = new FieldOfView((IMap<ICell>)this);

But if there is a better way, I would like to do that. Resharper does throw me a warning when I cast Map to IMap<ICell> saying:

Suspicious cast: there is no type in the solution which is inherited from both Map and IMap<ICell>.

EDIT2: Look's like neither of the casts worked. I've decided instead to make Map be derived from IMap and just create the Cell objects where needed in the code.

Thanks @Rob and @MK87 for your help!


Solution

  • No, IMap<Cell> is not the same as IMap<ICell>, so this line:

    _fieldOfView = new FieldOfView(this as IMap<ICell>);
    

    will always pass null as parameter.

    Yes, this is definitely solvable with variance.

    For example, you can have:

    IEnumerable<object> list = new List<string>();
    

    since list is IEnumerable<outT>, that means that every IEnumerable<TT> with TT that derives from T is a valid value for list. So the List doesn't have to be of object, it can be of any derived type.

    But because you can't use variance, we need another hack.

    Possible solution: instead of deriving Map from IMap<Cell>, derive it from IMap<ICell>. You'll have only to correct some points, for example the return type of GetCell() must become ICell instead of Cell. Is it feasable for you?