DEV Community

Mateus Cechetto
Mateus Cechetto

Posted on

Understanding Method Hiding and Overriding in C#: A Real-World Problem Solved

I recently faced a problem at my work that revolved around populating dictionaries depending on interfaces, but one of the derived types wasn't showing up in the expected collection. After digging deeper, I came across a concept that was new to me: method hiding. It was causing my derived class to be unsuitable for the interface. I was familiar with method overriding, but I had never encountered method hiding before.

In this article, I'll walk through the problem I faced, how I solved it, and explain the concepts and differences between method hiding and method overriding.

The Problem

I was working on a system where cards were represented by classes implementing ICard interface. Cards typically implement ICard indirectly through other interfaces, such as ICardWithHighlight and ICardWithRelatedCards, which extend ICard.

public interface ICard
{
    string GetCardId();
}

public interface ICardWithHighlight : ICard
{
    HighlightColor ShouldHighlight(Card card);
}

public interface ICardWithRelatedCards : ICard
{
    bool ShouldShowForOpponent(Player opponent);
    List<Card?> GetRelatedCards(Player player);
}
Enter fullscreen mode Exit fullscreen mode

The system uses reflection to dynamically load all the card classes and check which interface they implement. The goal is to populate dictionaries with card instances, keyed by their unique CardId.

The card Arcanologist has 2 different ids; therefore, it has 2 card classes: Arcanologist and ArcanologistCore, each implementing GetCardId() with its respective value. Since they refer to the same card, I used inheritance so that their ShouldHighlight() method remains consistent across both classes. However, I faced a problem: the derived class ArcanologistCore wasn't been added to the HighlightCards dictionary, even though it seemed to be a valid implementation of ICardWithHighlight.

public class RelatedCardsManager
{
    private Dictionary<string, ICardWithRelatedCards>? _relatedCards;
    private Dictionary<string, ICardWithHighlight>? _highlightCards;

    public Dictionary<string, ICardWithRelatedCards> RelatedCards => _relatedCards ??= InitializeRelatedCards();
    public Dictionary<string, ICardWithHighlight> HighlightCards  => _highlightCards ??= InitializeHighlightCards();

    private Dictionary<string, ICardWithRelatedCards> InitializeRelatedCards()
    {
        var (relatedCardsDict, highlightCardsDict ) = InitializeCards();
        _highlightCards = highlightCardsDict;
        return relatedCardsDict;
    }

    private Dictionary<string, ICardWithHighlight> InitializeHighlightCards()
    {
        var (relatedCardsDict, highlightCardsDict ) = InitializeCards();
        _relatedCards = relatedCardsDict;
        return highlightCardsDict;
    }

    private (Dictionary<string, ICardWithRelatedCards>, Dictionary<string, ICardWithHighlight>) InitializeCards()
    {
        var cards = Assembly.GetAssembly(typeof(ICard)).GetTypes()
            .Where(t => t.IsClass && !t.IsAbstract && typeof(ICard).IsAssignableFrom(t));

        var relatedCardsDict = new Dictionary<string, ICardWithRelatedCards>();
        var highlightCardsDict = new Dictionary<string, ICardWithHighlight>();

        foreach(var card in cards)
        {
            var cardInstance = Activator.CreateInstance(card) as ICard;

            if(cardInstance is ICardWithRelatedCards relatedCard)
            {
                relatedCardsDict[relatedCard.GetCardId()] = relatedCard;
            }

            if(cardInstance is ICardWithHighlight highlightCard)
            {
                highlightCardsDict[highlightCard.GetCardId()] = highlightCard;
            }
        }

        return (relatedCardsDict, highlightCardsDict);
    }
}
Enter fullscreen mode Exit fullscreen mode

In the code above: - Reflection is used to load all types in the assembly that implements the ICard interface. - We check if the type implement the ICardWithHighlight or ICardWithRelatedCards to add it to the appropriate dictionary. - Lazily initialize the Dictionaries, and when one is initialized, also initializes the other one.

Now, let's take a look at the Arcanologist and ArcanologistCore classes:

public class Arcanologist : ICardWithHighlight
{
    public string GetCardId() => HearthDb.CardIds.Collectible.Mage.Arcanologist;

    public HighlightColor ShouldHighlight(Card card) =>
        HighlightColorHelper.GetHighlightColor(card.GetTag(GameTag.SECRET) > 0);
}

public class ArcanologistCore : Arcanologist
{
    public new string GetCardId() => HearthDb.CardIds.Collectible.Mage.ArcanologistCore;
}
Enter fullscreen mode Exit fullscreen mode

The Issue

The problem occurred when ArcanologistCore wasn't appearing in the HighlightCards dictionary. Even though ArcanologistCore correctly implemented ICardWithHighlight and had its own GetCardId() method, it was still being ignored when populating the dictionary.

My first solution was to manually add ICardWithHighlight to the ArcanologistCore class. This worked, and the card started appearing in the dictionary.

public class ArcanologistCore : Arcanologist, ICardWithHighlight
{
    public new string GetCardId() => HearthDb.CardIds.Collectible.Mage.ArcanologistCore;
}
Enter fullscreen mode Exit fullscreen mode

But I was not satisfied with this solution. I have always knew that in OOP when a class extends other, it should be implementing all its interfaces, so why did I need to explicitly write that my derived class was implementing that interface? So I start looking into an answer for this, and I found it. It is called Method Hiding.

What is Method Hiding and Method Overriding?

In C#, method hiding occurs when a derived class defines a method with the same name as a method in the base class, but without using the override keyword. This causes the derived class's method to hide the base class's method. When using method hiding, the base class method is not called — instead, the method in the derived class is used only if the reference is specifically of the derived type. This way, since GetCardId() in the base class was hidden, ArcanologistCore no longer fully implemented the ICard interface, and as a result, it did not implement ICardWithHighlight either.

Method overriding, on the other hand, happens when a derived class uses the override keyword to provide its own implementation of a method from the base class. This ensures that the method in the derived class is called, even if the reference is of the base class type.

The Solution

The solution was straightforward: instead of using the new keyword in the GetCardId() method in ArcanologistCore, I needed to use override to properly override the method from the base class. This ensures that when an object of type ArcanologistCore is referenced as an ICardWithHighlight, the overridden method is called, allowing the card to be added to the dictionary. I also had to mark the base class method as virtual to tell C# that the method can be overridden.

Here is the corrected implementation:

public class Arcanologist : ICardWithHighlight
{
    public virtual string GetCardId() => HearthDb.CardIds.Collectible.Mage.Arcanologist;

    public HighlightColor ShouldHighlight(Card card) =>
        HighlightColorHelper.GetHighlightColor(card.GetTag(GameTag.SECRET) > 0);
}

public class ArcanologistCore : Arcanologist
{
    public override string GetCardId() => HearthDb.CardIds.Collectible.Mage.ArcanologistCore;
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

After changing the new keyword to override in the derived class, the card was properly added to HighlightCards dictionary, and the issue was resolved. This experience helped me understand the distinction between method hiding and method overriding in C#.

Top comments (0)