Search code examples
c#inheritancepolymorphismcard

Why isn't the new keyword respected by base class methods?


I am making a complicated card game. So I have a class Card, which has some attributes specific to this game, for example a List<Effect> effects. The class also has some basic methods to parse its effects into readable English, GetParsedEffects() that works through the list and parses them into a string.

public class Card {
   public List<Effect> effects;
   public string GetParsedEffects() {
      foreach(Effect e in effects)
         //formatting stuff
   }
}

Now I decided I wanted to create a very special effect where you create an identical twin of a card. When I say identical twin, I mean a card which always has the same values as another card, and when either of them update a value, both are affected. It is like having 2 instances of the very same card (but they cannot be completely the same, as when you play one of them, I need to know which one it is so that not both of them go to the discard pile.)

I tried to do this by using inheritance. So I created a child class TwinCard : Card. The TwinCard has an additional property, Card original, and uses the new keyword on all the properties so that they get and set the properties of the original card instead of its own. So basically, you have an original card, and then the twin-card which just reflects the original one, and alterations to the twincard affects the original card. Since both share Card as class, they can be used by all methods just the same.

public class TwinCard : Card {
   public Card original;
   public new List<Effect> effects { get { return original.effects;} set { original.effects = value; } }

   public TwinCard(Card original) {
      this.original = original;
   }
}

I was so pleased with myself.

Until I tried to call the GetParsedEffects() on all cards in hand, including the TwinCard. Since this is a base class method, it seems it uses the base class field effects instead of the TwinCard version marked with the new keyword. So it tried to use the supposedly hidden effects of the base class, instead of the new List<Effect> effects.

I was under the impression that the new keyword means that any and all calls would be exchanged for the new version, but it looks like that is not the case. Am I doing something wrong, or did I misunderstand how new works?

I'm thinking this has to do with the way I iterate through the hand, defining the cards as Card;

foreach(Card c in hand) {
   c.GetParsedEffect();
}

Case in point: I have a Card named Concentrate with 1 effect in its list. I have a TwinCard with its original set to Concentrate. When in debug mode (Visual Studio), I can see 2 fields named "effects", one is empty and the other has the effect referenced in Concentrate. When I enter GetParsedEffects() on the TwinCard, it shows the effects as being 0 in the list, supposedly it uses one of the effects fields, but not the other.

If this is a C# bug, how would I go around it? If this is expected behavior, how would I otherwise design it? The only other option I see is to implement events and listeners for changes in each and every attribute, and have the TwinCard update its own values depending on those listeners. But that is a giant web of listeners.


Solution

  • You misunderstood the new keyword. new hides the original member from code that uses that class, not from the base class. What you want is virtual, a virtual member that is overrided in a class replaces the base one. Look this example:

    public class Card
    {
        public virtual string eff { get; set; } = "basecard";
    
        public void PrintEffect()
        {
            Console.WriteLine(eff);
        }
    }
    
    public class NewCard : Card
    {
        public new string eff { get; set; } = "newcard";
    }
    
    public class VirtualCard : Card
    {
        public override string eff { get; set; } = "virtualcard";
    }
    
    public static void Main()
    {
        Card c = new Card();
        c.PrintEffect();
        Console.WriteLine(c.eff);
        NewCard cs = new NewCard();
        cs.PrintEffect();
        Console.WriteLine(cs.eff);
        VirtualCard cv = new VirtualCard();
        cv.PrintEffect();
        Console.WriteLine(cv.eff);
    }
    

    You will get a result of

    basecard
    basecard
    basecard
    newcard
    virtualcard
    virtualcard
    

    As you see, the new property is only seen by the code that uses the explicit type NewCard, but the virtual is seen by the new code and the base code.

    Also, to demostrate it check this:

    Card c = new NewCard();
    c.PrintEffect();
    Console.WriteLine(c.eff);
    

    What do you think it will print?

    In case you thought "basecard newcard"... well, you're wrong, it will show "basecard basecard" because we're using the type Card instead of NewCard so this code doesn't 'sees' the new property.