1

How do I need to restructure or refactor my code, so that I can use embedded and inherited objects to work as expected with least code repetition. this code is only an example, I think it is a basic error in my OOP-thinking...

The example would be the same with almost all common base classes in books like Car and Engine and Driver: When I add special classes that inherit from base like Racecar:Car and TurboEngine:Engine and add properties to the specialized class like boost to TurboEngine I would run into the same problem, when I want to use as much as code and methods from the base classes without overriding almost all methods.

I already refactored my fields to properties and tried with new and override but the logical problem stays the same.

If that code is too much I can try to shorten it more to the single problem I stuck in.

the examle code shows the problems. up/down casting won't help. I guess it's more a basic thinking error as OOP programming is hard to learn for an old assembler guy.

EDIT----UPDATED:

okay it's by design. https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) https://en.wikipedia.org/wiki/Liskov_substitution_principle

I feel a bit releaved now...stuck there and thought: can't be true. There should be more places to point to, gather and demonstrate these common mistakes depending on the level of experience of developers. Also in many books it is far from highlighted. Now knowing the correct term for it, I find more. But still many deep technical explanations not suitable for the novice. That could save a lot of time :)

var root = new Vector2D(0,0);

GameField2D fGame = new GameField2D(10,10);
fGame.Init();
fGame.Field[1,1].ParentPoint = root;

fGame.Field[1,1].Color = 1; //<--ERROR to be expected
var test = (TetrisField2D)fGame; //<---ERROR cast not possible

TetrisField2D fTetris = new TetrisField2D(10,10);
fTetris.Init();
fTetris.Field[1,1].ParentPoint = root; //<--ERROR Nullreference because only base class field is initiated.

Here are the classes:

the inheriting one:

public class TetrisField2D: GameField2D {
    public int Lifes;
    public TetrisPoint[,] Field; //I want of course Tetrispoints with color

    public TetrisField2D(): base(){}
    public TetrisField2D(int x,int y) : base (x,y) {}
}

Base class

public class GameField2D { //"generic"
    public GamePoint[,] Field;
    private int size;

    //CTORs
    public GameField2D(){
      Field = new GamePoint[9,9];
      size = 10;
    }
    public GameField2D(int x,int y){
      Field = new GamePoint[x-1,y-1];
      size= x-1;
    }
    public GameField2D(GamePoint[,] f){
      Field = f;
      size = f.GetLength(0);
    }
    public void Init() {
      for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j++) {
            Field[i,j] = new GamePoint();
        }
      }
    }
}

included point classes:

public class TetrisPoint : GamePoint{
  public int Color;

  public TetrisPoint(){}
  public TetrisPoint(int x, int y, int col) : base(x,y) {
    Color = col;
  }
}
public class GamePoint   { 
  public Vector2D ParentPoint;
  public bool Appears;
  public int IsUsed;

  public GamePoint(){   }   
  public GamePoint(int x, int y, bool a=false) {
    this.ParentPoint = new Vector2D(x,y); 
    Appears = a;
   }
}

public struct Vector2D {
   public int X,Y;
   public Vector2D(int x,int y) {
        this.X=x;
        this.Y=y;
   }
}
Falco Alexander
  • 3,092
  • 2
  • 20
  • 39

2 Answers2

8

The example would be the same with almost all common base classes in books like Car and Engine ... When I add special classes that inherit from base like Racecar : Car and TurboEngine : Engine and add properties to the specialized class like boost to TurboEngine I would run into the same problem, when I want to use as much as code and methods from the base classes without overriding almost all methods.

The problem you are running into is a classic problem of object oriented design. OO works well to model situations where derived classes are more specific but also do not introduce restrictions. A race car is more specific than a car, but "a race car only has a turbo engine" is a restriction on the kinds of engines you can put in a car.

There are no slam dunk obvious ways to solve this problem. I wrote a series of posts on my blog describing the problem in more detail and some ways people have attempted to solve it; you might find that interesting.

http://ericlippert.com/2015/04/27/wizards-and-warriors-part-one/

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • I feel a bit releaved now...stuck there and thought: can't be true. there should be more places to gather and demonstrate these common mistakes *depending* on the level of some developers. that could a lot of time :) – Falco Alexander Oct 31 '15 at 15:51
1

You cannot cast from a base class to a derived class, only the other way around. You will have to create a TetrisField instead of the base GameField.

GameField2D fGame = new TetrisField2D(10,10);

Unfortunately C# does not support property covariance (I really wish it did as this has happened to me before.), so to get around the issue of having a different type of Field, you will need to use the new keyword in your TetrisField class. However, it is very important that you only modify the field when casted to TetrisField, or else you will actually be modifying the base class's Field.

Community
  • 1
  • 1
Cyral
  • 13,999
  • 6
  • 50
  • 90
  • I know, but your example won't work because GameField2D does not contain .color....also also need to edit: the term seems 100% fitting my problem which is hard to google **Return type covariance is where you override a base class method that returns a less-specific type with one that returns a more specific type** – Falco Alexander Oct 31 '15 at 15:14
  • @FalcoAlexander See what I said on covariance. I would just write your example code without the casting, create a TetrisField2D and assign it to a TetrisField2D and don't try and cast down to the base class, there is no reason to. – Cyral Oct 31 '15 at 15:16
  • Got it now! are there some best practices or c# patterns or workaround for this to point me to? – Falco Alexander Oct 31 '15 at 15:21
  • 1
    There is [somewhat of a workaround using virtual methods](http://stackoverflow.com/a/157137/1218281), but I've never found a good solution to this problem when using this pattern. – Cyral Oct 31 '15 at 15:23
  • I should also say that there is a popular proposal for [C# 7 to include covariant return types.](https://github.com/dotnet/roslyn/issues/357) – Cyral Oct 31 '15 at 15:27