0

So i'm having a problem with generic-types/inheritence in c#, this is the code

   public class Tile{
       ...
       public TileMap<Tile> tileMap{ get; set; }
       ...
   }

   public class TileMap<T> where T : Tile{
       ...
       T newTile = (T)Activator.CreateInstance(typeof(T));
       newTile.tileMap = this; //no compile
       ...
   }

The compiler says: cannot convert TileMap<T> to TileMap<Tile>

I could make a generic type in Tile that I always give the same type as the tile itself, but this doesn't seem nice to me (and possible for mistakes).

I think i need some kind of generic type that automatically uses itself (the most sub-class)

(there are subclasses of Tile that should work in the TileMap)

This is kind of the idea that I want:

     public class Tile<T> where T : this{
         ...
         public TileMap<T> tileMap { get; set; }
         ...
      }

But than without having to initialise the generic type, because it should always be the same as the class (and if i use a subclass of Tile, T should be the subclass).

Louis
  • 3
  • 2
  • 1
    Not an answer: Instead of using Activator, you can use the `new()` constraint and say new T(). so `where T : Tile, new()` ... `T newTile = new T();` – Furkan Kambay Jan 20 '18 at 02:04
  • Duplicate explains why your code fails. Your solution to use "curiously recursive generic" is perfectly fine, but unfortunately there is no way to get restriction you want (enforce passing "self" type as parameter)... – Alexei Levenkov Jan 20 '18 at 02:29
  • (https://stackoverflow.com/questions/8866071/reflexive-type-parameter-constraints-xt-where-t-xt-any-simpler-alterna for more reading on the pattern's limitations) – Alexei Levenkov Jan 20 '18 at 02:36
  • Thank you Furkan, this really is good to know! – Louis Jan 20 '18 at 03:06

2 Answers2

1

Two approaches for you, OP.

1. The "right way" (using covariance)

It can be done using covariance, which is pretty hard to explain, but easy to show. Covariance requires that you use interfaces. This will compile and work:

public interface ITileMap<out T> where T : Tile
{
}

public class Tile
{
    public ITileMap<Tile> tileMap { get; set; }
}

public class TileMap<T> : ITileMap<T> where T : Tile, new()
{
    public TileMap()
    {
        T newTile = new T();
        newTile.tileMap = this;   //This compiles now!!! 
    }

    public override string ToString()
    {
        return "Hello world!!!";
    }
}

public class Program
{
    public static void Main()
    {
        var t = new TileMap<Tile>();
        Console.WriteLine(t);
    }
}

Output:

Hello world!!!

Code available on DotNetFiddle

The key is this line:

public interface ITileMap<out T> where T : Tile

The out keyword tells c# that a ITileMap<base> class can refer to a ITileMap<derived> interface. With generics, you have to be explicit about this.

While regular inheritance allows you to assign base x = derived, c# does not allow TileMap<base> x = TileMap<derived>. Why? They don't inherit from each other. Instead, they both "inherit" (not really) from something else, a common ancestor, TileMap<>, which is their generic type definition. A type definition cannot be used as a type, so the way we deal with things like this in the generic world is with variance. In this specific case, you need covariance, e.g. the out keyword, which is only allowed in an interface.

2. The easy way

Covariance is pretty hard for a lot of people to get. You can avoid the whole issue, get rid of the interfaces, and skip the out keyword, simply by introducing an intermediary class, like this:

public class TileOnlyMap : TileMap<Tile>

Now your code will compile:

public class TileOnlyMap : TileMap<Tile>
{
    public TileOnlyMap()
    {
        Tile newTile = new Tile();
        newTile.tileMap = this;
    }
}

In this example, we define a new class, TileOnlyMap, which doesn't take any generic parameters itself, but inherits from a TileMap<Tile> where the generic type parameter is determined at compile time. When you do this, the main thing you lose is early type binding. You can still use the TileOnlyMap to contain subclasses of Tile, only now they will be stored with runtime polymorphism instead of generic type compatibility and variance (the cost of this is negligible). Example:

public class SpecificTypeOfTile : Tile
{
}

public class TileOnlyMap : TileMap<Tile>
{
    public TileOnlyMap()
    {
        var newTile = new Tile();
        newTile.tileMap = this;

        var newTile2 = new SpecificTypeOfTile();  //This works because of polymorphism
        newTile2.tileMap = this;
    }
}

See the full, second example on DotNetFiddle.

Personally, unless you have carefully planned your generic object model, I would stick with the far simpler second example. If you get your variance/interfaces/object model wrong, you will really create a serious mess!! On the other hand, if you understand co/contravariance and have planned out a generic object scheme, the first answer is the preferred approach. And I promise it will make much more sense the more you work with it.

John Wu
  • 50,556
  • 8
  • 44
  • 80
1

If you can make an interface for TileMap, and it only uses T as a return, then you can utilize covariance:

public interface ITileMap<out T> where T : Tile
{
    T DoSomething();

    //this solution won't work if you need something like this:
    //void DoSomethingElse(T tile);
}

Then modify the Tile property to take the interface instead:

public class Tile
{
    public ITileMap<Tile> tileMap{ get; set; }
}

And then the implementation of TileMap:

public class TileMap<T> : ITileMap<T> where T : Tile
{

    public T DoSomething()
    {
        T newTile = (T)Activator.CreateInstance(typeof(T));
        newTile.tileMap = this;
        return newTile;
    }
}
Dave M
  • 2,863
  • 1
  • 22
  • 17