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.