0

TL/DR:

  • SpecificType is a subclass of GeneralType
  • GeneralType and SpecificType both have methods with the same name and parameters "doSomething()". There are other subclasses of GeneralType that don't bother to define their own method with that name, and just implicitly GeneralType's implementation.
  • The problem: you have a SpecificType object that was passed to you as a GeneralType, and when you call its doSomething() method, it calls GeneralType.doSomething() instead of SpecificType.doSomething(), because the compiler normally cares only about its declared type at compile-time, not its actual type at runtime.
  • Solution: declare GeneralType.doSomething() as 'virtual', and declare SpecificType.doSomething() as 'override'.

Original scenario, example code, and question follows.

Suppose you have a chess implementation where:

class ChessPiece {

    public void promoteIfPawnAndPromoted(ChessPieceType newType) {
        return; // Do nothing
    }
}

class Pawn : Chesspiece {

    // This is not `override` nor `new`:
    public void promoteIfPawnAndPromoted(ChessPieceType newType) {
        /* Do stuff */
    }
}
  • Over the course of its life, the Pawn object gets created by a factory method whose declared return type is ChessPiece, then gets stored and passed around at various points as a ChessPiece, or an element of List<ChessPiece>, HashSet<ChessPiece>, or ChessPiece[].

  • The Pawn object will NEVER be anything BUT a ChessPiece or Pawn. It will never, for instance, be a String or a bool. If I try to use it as anything besides a ChessPiece or Pawn, I want the compiler to complain & complain LOUDLY.

  • Nevertheless, at runtime, if I call the promoteIfPawnAndPromoted() method of a ChessPiece that happens to actually BE a Pawn, I want it to use Pawn's method, not ChessPiece's.

Is there a way, as of the current version of C#, to declare the class so that I

  • preserve strong compile-time type checking (ie, a variable or argument MIGHT ultimately be a Pawn, Rook, Knight, or any other ChessPiece... but it will absolutely never be a String or an int)

  • nevertheless, recognize at compile-time that any given ChessPiece object might plausibly match two possible variants of promoteIfPawnAndPromoted(), and ask the ChessPiece instance ITSELF (at runtime) which one it ought to bind to?

I'm pretty sure there's an official "nice" way to concisely declare this now in C# (vs doing manual runtime if/then testing via instanceof), but I'm not quite sure what it is. I'm coming from Java, and I'm still shaky on the distinction between 'dynamic' and 'var' (not to mention other things that can be in a C# method declaration, like the distinction between declaring a superclass method with 'virtual' vs declaring a subclass method with 'new'). Experience-wise, I have enough experience from Java to recognize that I'm wading around in a minefield with quicksand, but not enough experience with C# to avoid sinking or getting blown up. ;-)

I'm probably wrong, but as I understand it as of this moment...

  • If I declare the type as 'dynamic', I'm totally throwing the compile-time type-checking baby out with the bath water. I can't be assured something that MIGHT be a Pawn, but will always & ABSOLUTELY be at least a ChessPiece, can't ever possibly be something like a String.

  • If I declare the type as 'var', the compiler will still use static binding... it just does it automatically at compile-time. So if it sees that my 'var' is always being passed a ChessPiece, it'll still statically-bind to ChessPiece's method, as opposed to waiting until runtime to see whether the ChessPiece is actually a Pawn.

  • My hunch is that declaring the superclass method as 'virtual' might be a step in the right direction.

  • At the moment, I'm not convinced I fully understand the distinction between declaring an overriding method as 'new' vs 'override' in the context of an overridden virtual method from a superclass.


Update #1 (wrong):

According to [runtime type vs compile-time type method invocation, it looked like all I had to do was:

public virtual void promoteIfPawnAndPromoted(ChessPieceType newType) {
    return;
}

Then declare it in Pawn as:

public new void promoteIfPawnAndPromoted(ChessPieceType newType) {
    // Pawn-specific implementation...
}

This didn't work. Calling promoteIfPawnAndPromoted(newType) on a Pawn identified as a ChessPiece (ChessPiece method declared with virtual, Pawn method declared with new) still resulted in the 'ChessPiece' variant getting called, not Pawn's variant.

Update #2 (correct):

Replacing 'new' with 'override' in Pawn's method declarations seems to have worked. Invoking promoteIfPawnAndPromoted(newType) on a Pawn that's declared to be ChessPiece now seems to be calling the Pawn variant & not the ChessPiece variant... but I'm not yet 100% confident that this is the complete solution (I've gotten bitten by enough things over the years to get to the point where I now care whether some working solution is 'officially correct', as opposed to 'compiles without errors, and seems to work right now')

public virtual void promoteIfPawnAndPromoted(ChessPieceType newType) {
    return;
}

Then declare it in Pawn as:

public override void promoteIfPawnAndPromoted(ChessPieceType newType) {
    // Pawn-specific implementation...
}
Bitbang3r
  • 6,826
  • 5
  • 27
  • 40
  • It looks like you want `promoteIfPawnAndPromoted` to overwrite `this` (i.e. `this /* a pawn */ = new Queen()` when a pawn reaches the edge of the chessboard. You cannot do that in C#. You need to consider a different program design. I don't think any `ChessPiece` subclass should be concerned with promotion rules or the job of replacing a chess-piece. Look up "separation of concerns". – Dai Nov 25 '18 at 00:55
  • It's really just chaining through (when called via Pawn) to the method in BoardState that does the real work. I waver back and forth about whether it's more elegant to directly call a method in BoardState that takes the ChessPiece as an argument, tests whether it's a pawn, and tests whether it was just promoted, or to let the type of the ChessPiece itself imply the isPawn() test, and chain through to BoardState's promotePawn() method ONLY when the piece already knows it's a Pawn eligible for promotion. – Bitbang3r Nov 25 '18 at 01:43
  • I'm not sure what this post asks about. If you don't have question consider making it blog post somewhere or maybe self-answered Q&A pair... – Alexei Levenkov Nov 25 '18 at 03:10
  • Its "question" nature becomes apparent when you look at its 'edit' history. It started out as a question, then got edited into a mini-article (with concise summary, followed by exhaustive detail & documented search for a solution) after madreflection authoritatively answered it. The later-added "TL/DR" heading boils it down to its bare essence for people looking for a quick "does this apply to my problem" answer, while the remainder allows someone who's interested to follow the original details and train of thought. All of the comments came AFTER the question was answered. – Bitbang3r Nov 25 '18 at 18:34

1 Answers1

2

You're using the wrong keyword in the Pawn class. You want to use override because it causes the v-table lookup to find the derived class's implementation. The new keyword has a very different purpose.

See Knowing When to Use Override and New Keywords

madreflection
  • 4,744
  • 3
  • 19
  • 29