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 isChessPiece
, then gets stored and passed around at various points as aChessPiece
, or an element ofList<ChessPiece>
,HashSet<ChessPiece>
, orChessPiece[]
.The Pawn object will NEVER be anything BUT a
ChessPiece
orPawn
. It will never, for instance, be aString
or abool
. If I try to use it as anything besides aChessPiece
or Pawn, I want the compiler to complain & complain LOUDLY.Nevertheless, at runtime, if I call the
promoteIfPawnAndPromoted()
method of aChessPiece
that happens to actually BE a Pawn, I want it to use Pawn's method, notChessPiece
'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...
}