-1

What I want to do:

abstract class TileBase
{
    protected TileGroup<TileBase> tileGroup;
}
class Tile : TileBase
{
    public Tile(Province Province)
    {
        tileGroup = Province;
    }
}
abstract class TileGroup<T>
{
    protected T[] tiles;
    protected TileGroup<TileGroup<T>> tileGroup;
}
class Province : TileGroup<TileBase>
{

    public Province(Tile tile, Nation nation)
    {
        tiles = new[] { tile };
        tileGroup = nation;
    }
}
class Nation : TileGroup<Province>
{

    public Nation(Province province)
    {
        tiles = new[] { province };
        tileGroup = null;
    }
}

This will not work because of invariance (if I understand invariance correctly): cannot convert Nation to TileGroup<TileGroup<TileBase>>

So I'll need to write it like this:

class Nation : TileGroup<TileGroup<TileBase>>
{

    public Nation(Province province)
    {
        tiles = new[] { province };
        tileGroup = null;
    }
}

But when layers get stacked; this gets ugly fast:

Map : TileGroup<TileGroup<TileGroup<TileBase>>> 

This also makes adding layers between two existing layers difficult because one change in a low layer means changing all the higher layers.

So how exactly should I be doing this?


Sorry for the formulation, I know what I want, but not how I should explain it clearer than in this way.

Michiel
  • 57
  • 1
  • 6
  • I know this is a basic answer but you could just create another class that acts like a TileGroup> data structure. –  Aug 20 '17 at 11:53
  • Can you show `TileBase` and `Tile` too, please? – poke Aug 20 '17 at 11:57
  • @poke - done, didn't think they were really relevant here – Michiel Aug 20 '17 at 12:14
  • @Mel O'Hagan - yes, but map: TileGroup won't work, so it's not a solution for more than 1 layer, where I need a structure for an arbitrary amount of layers – Michiel Aug 20 '17 at 12:18
  • Perhaps it will be easier to answer if you described what you want to do in words, instead of in code that can't compile. – Zohar Peled Aug 20 '17 at 12:28
  • @Michiel maybe you should do away with trying to put it all in an inheritance chain and instead tried "composition" model. It's a long shot since I don't know how exactly you want your program to work but here's something to get you started: [1.](http://www.c-sharpcorner.com/UploadFile/ff2f08/inheritance-vs-composition/) [2.](https://en.wikipedia.org/wiki/Composition_over_inheritance) – Jakub Dąbek Aug 20 '17 at 12:32
  • @Zohar Peled - I tried to do that, but thats really hard for me as I don't really understand why it doesn't work ^^' the code can actually compile when you replace Nation : TileGroup with Nation : TileGroup> as said in the question though – Michiel Aug 20 '17 at 12:34
  • @Jakub Dąbek - I already use composition where a nation has provinces and a province has tiles :) . The reason I want to use inheritance is because the relation between province and tile resembles the relation between nation and province. I want to reuse the construction code of a province to construct a nation. – Michiel Aug 20 '17 at 12:41

1 Answers1

0

In order to understand what is going on and why this isn’t working, you need to understand what generic types actually are. In a way, they are a kind of template that provide exact types for every valid T as they are requested by the language.

When you write TileGroup<TileBase> what actually happens is that a new type, let’s call it TileGroup_TileBase is defined that looks like this:

class TileGroup_TileBase
{
    protected TileBase[] tiles;
    protected TileGroup<TileGroup<TileBase>> tileGroup;
}

Now, let’s continue this expansion of generic types for a bit. We already know the TileGroup<TileBase> type in there, but there’s also a TileGroup<TileGroup_TileBase>, so let’s replace that:

class TileGroup_TileBase
{
    protected TileBase[] tiles;
    protected TileGroup_TileGroup_TileBase tileGroup;
}
class TileGroup_TileGroup_TileBase
{
    protected TileGroup_TileBase[] tiles;
    protected TileGroup<TileGroup<TileGroup_TileBase>> tileGroup;
}

We could continue here, but this is actually enough to explain the problem. Let’s take a look at Nation instead which is what you are trying to assign in the Province constructor. Nation is a TileGroup<Province>, so let’s expand that:

class TileGroup_Province
{
    protected Province[] tiles;
    protected TileGroup<TileGroup<Province>> tileGroup;
}

Okay, so we have the types expanded enough. Let’s take a look at the assignment you are trying to do. In Province, the tileGroup property is of type TileGroup<TileGroup<TileBase>>, so essentially this is what you are trying to do:

TileGroup<Province> nation = null;
TileGroup<TileGroup<TileBase>> province_tileGroup = nation;

Can you see why this fails now? If not, let’s use our expanded generic types here instead:

TileGroup_Province nation = null;
TileGroup_TileGroup_TileBase province_tileGroup = nation;

Okay, that are the actual types that are used (remember that we are not doing this just to understand this, but that those generic types get actually materialized for each T for real!). But if we look at the definitions above, TileGroup_Province and TileGroup_TileGroup_TileBase are not actually related. Sure, they look similar, but there is no type relationship that would allow this kind of assignment!

That’s actually why when we deal with generic types in the BCL, we so often have interfaces. Because interfaces allow us to have a type relationship between those generic type materializations which we can then use to assign one to another. To be honest though, your abstract classes with those protected fields make it a bit difficult to clean this up with interfaces, so you should probably think about whether you actually need this kind of type relationships and whether having an abstract base type for those fields is actually necessary.

poke
  • 369,085
  • 72
  • 557
  • 602
  • So with TDerived : TBase, Generic b = (TDerived) d; just doesn't work because of the underlying types. (Quite counter-intuitive to me, but sure.) Is this because of the way it is implemented or shouldn't it make sense to try this anyway? (Is this a design flaw in C# because of complexity or would implementing this in C# just be bad design?) – Michiel Aug 20 '17 at 13:19
  • 1
    I don’t think this is a design flaw; this is merely a limitation of the type system around generic types. I’m not sure if there was a way to actually fix this so inheritance with generic types would “just work”. Whether it’s counter-intuitive or not depends on how you interpret generic types to work. Anyway, the “proper solution” is to avoid type relationships based solely on generic inheritance and use interfaces instead. – poke Aug 20 '17 at 13:29