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);
}
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);
}
}
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;
}
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;
}
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;
}
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)