17

I'm working on a project that involves a lot of interfacing and inheritance, which are starting to get a little tricky, and now I've run into a problem.

I have an abstract class State which takes in a Game object as a constructor argument. In my Game class's constructor, it takes in a State. The idea is that when inheriting from the abstract base Game class, when calling the base class's constructor, you give it an initial State object. However this State object takes in the same Game that you're creating it in. The code looks like this:

public class PushGame : ManiaGame
{
     public PushGame() :
          base(GamePlatform.Windows, new PlayState(this), 60)
     {
     }
}

However this doesn't work. I can only assume because the 'this' keyword is not usable until after the constructor has begun to execute. Trying to use it in your base class's constructor doesn't work, apparently. So what would be my best workaround for this? My plan B is to just remove the State argument from the Game class's constructor and just set the state inside the constructor code afterwards.

Is there an easier, less-intrusive way of doing this?

John Saunders
  • 160,644
  • 26
  • 247
  • 397
William Thomas
  • 2,108
  • 3
  • 22
  • 32

5 Answers5

9

Clearly the ManiaGame class always uses objects of PlayState type, so you can move the creation at the ManiaGame level:

public class PushGame : ManiaGame
{
     public PushGame() : base()
     {
     }

}

public class ManiaGame
{
    PlayState ps;   
    public ManiaGame() {
        ps = new PlayState(this);
    }
}

If you want more concrete PlayState classes..

public class PushGame : ManiaGame
{
     public PushGame() : base()
     {
     }
     protected override PlayState CreatePlayState()
     {
        return new PushGamePlayState(this);
     }
}

public class ManiaGame
{
    PlayState ps;   
    public ManiaGame() {
          ps = CreatePlayState();
    }
    protected virtual PlayState CreatePlayState()
    {
        return new PlayState(this);
    }
}

public class PlayState
{
   public PlayState(ManiaGame mg) {}
}


public class PushGamePlayState : PlayState
{
   public PushGamePlayState(ManiaGame mg) : base(mg){}
}
Adrian Iftode
  • 15,465
  • 4
  • 48
  • 73
  • +1 and it also highlights the co-dependence between ManiaGame and PlayState, so the 2 objects can never be cross referenced "simultaneously" in the respective constructors. – StuartLC Feb 26 '12 at 07:04
  • 2
    It's important to mention that calling a virtual method from inside the ctor can be dangerous - http://stackoverflow.com/questions/119506/virtual-member-call-in-a-constructor – AlexD Feb 26 '12 at 12:45
  • In other words, use object **containment** instead of object **inheritance**. – Glenn Slayden Apr 30 '20 at 06:20
1

From the C# Language Specification

An instance constructor initializer cannot access the instance being created. Therefore it is a compile-time error to reference this in an argument expression of the constructor initializer, as is it a compile-time error for an argument expression to reference any instance member through a simple_name.

i.e. this can only be used to reference another constructor in the context of a constructor initializer, as the reference to the current object instance won't be available until the construction is completed.

i.e. this can be only used as a scoping keyword before the constructor executes:

    : this("ParameterForAnotherConstructor")

But it is not available as a reference to the class instance, since it has not completed construction

    : base(this) // Keyword this is not available in this context

And obviously we can't call any instance methods from the constructor initializer either

    : base(GetThis()) // Object reference is required

To solve OP's problem, changes to the base Mania class seems inevitable, given the two-way coupling between PlayState and ManiaGame (or subclasses of ManiaGame, like PushGame). There are many patterns available to decouple tight dependencies like this, such as the Dependency Inversion Principal (i.e. abstract the coupling between the classes), or the Observer pattern - (one of the two classes raises events or allows callbacks to be hooked (e.g. delegates or Actions), allowing the second class to 'observe' state changes without the hard coupling between them.

There is a similar Stack Overflow question, Keyword 'this' (Me) is not available calling the base constructor with a workaround similar to your suggestion.

StuartLC
  • 104,537
  • 17
  • 209
  • 285
1

If the State implementation used depends on the concrete Game class, then I would create a new instance of the State inside the constructor of the child Game class (PushGame) and access the State in the base class through the abstract property.

public class PushGame : ManiaGame
{
    private readonly PlayState gamePlayState;

    public PushGame() : base()
    {
        gamePlayState = new PlayState(this);
    }

    protected override State GamePlayState
    {
        get { return gamePlayState; }
    }
}

public abstract class ManiaGame
{
    protected abstract State GamePlayState { get; }
}

public class State
{
    public State(ManiaGame mg) { }
}


public class PlayState : State
{
    public PlayState(ManiaGame mg) : base(mg) { }
}
AlexD
  • 5,011
  • 2
  • 23
  • 34
0

I've recenly experienced this issue when trying to pass concrete instances of implemented interfaces via this to the base constructor.

To work around it, I simply implemented an abstract method on the base class that fetched the instance I was looking for:

public abstract class BaseClass
{
    ...

    protected abstract IMyInterface GetInterface();

    ...
}

public class DerivedClass : BaseClass, IMyInterface
{
    ...

    protected override IMyInterface GetInterface()
    {
        return this;
    }

    ...
}

In my base class code, you can then use GetInterface (or whatever type you need) to obtain the instance:

public abstract class BaseClass
{
    public void Foo()
    {
        GetInterface().DoSomething();
    }
}
Martin
  • 16,093
  • 1
  • 29
  • 48
  • Not an answer, because the OP would need to call your proposed abstract function from within the context of `: base(...)` (or `: this(...)` construction which has exactly the same problem. – Glenn Slayden Apr 30 '20 at 06:24
0

Does your design differentiate between a game (say backgammon) and a game in progress (a game of backgammon)? If you're trying to mix these two concepts then I would suggest modeling them separately. For example, Backgammon and BackgammonContest.

Jamie Ide
  • 48,427
  • 16
  • 81
  • 117