A fun teaching project demonstrating inheritance, abstract classes, and method overriding in C#.
# Build and run tests
dotnet build
dotnet test
# Run the Blazor web app
dotnet run --project MonsterArena.WebThen open https://localhost:5001 (or the port shown in terminal).
MonsterArena/
├── MonsterArena.Logic/ # Class library (the OOP concepts)
│ ├── Monster.cs # Abstract base class
│ ├── Dragon.cs # Derived class - overrides everything
│ ├── Goblin.cs # Derived class - minimal overrides
│ ├── Wizard.cs # Derived class - calls base methods
│ └── Arena.cs # Polymorphism demo
│
├── MonsterArena.Tests/ # Unit tests
│ ├── MonsterTests.cs # Tests for inheritance behavior
│ └── ArenaTests.cs # Tests for polymorphism
│
└── MonsterArena.Web/ # Blazor Server UI
└── Components/Pages/Home.razor # Interactive monster battles
Open Monster.cs and discuss:
-
Why
abstract class?public abstract class Monster
- We want a base type that defines common behavior
- But it doesn't make sense to create a generic "Monster"
- Every monster must be a specific type (Dragon, Goblin, etc.)
-
Try to instantiate it (show the error):
var m = new Monster("Test", 100); // COMPILE ERROR!
- Uncomment the test in
MonsterTests.csline 14-19 to demonstrate
- Uncomment the test in
-
Common properties stay in the base class:
public string Name { get; protected set; } public int Health { get; protected set; } public bool IsAlive => Health > 0;
- All monsters have these - no need to repeat them
Still in Monster.cs:
-
What are abstract methods?
public abstract int Attack(); public abstract string GetBattleCry();
- No implementation body - just a signature
- FORCES derived classes to provide their own implementation
- "Every monster attacks, but each attacks differently"
-
Compare implementations across monsters:
Monster Attack()GetBattleCry()Dragon 20-35 damage "ROOOAAAR! Feel my flames!" Goblin 5-15 damage "Hehehe! Stab stab!" Wizard 15-30 (uses mana) "By the power of the arcane!" -
What happens if you forget to override?
- Comment out
Attack()inGoblin.cs - Show the compile error: "Goblin does not implement inherited abstract member"
- Comment out
Compare virtual vs abstract:
| Keyword | Has Implementation? | Override Required? |
|---|---|---|
abstract |
No | Yes - must override |
virtual |
Yes (default) | No - optional |
Examples in Monster.cs:
// Virtual method WITH a default implementation
public virtual int Defend(int incomingDamage)
{
return (int)(incomingDamage * 0.75); // Take 75% damage
}
// Virtual method that returns null by default
public virtual string? SpecialAbility()
{
return null; // No special ability
}Show the three patterns:
-
Goblin - Uses defaults (doesn't override):
Defend()→ uses base class (75% damage)SpecialAbility()→ returns null
-
Dragon - Overrides with better behavior:
public override int Defend(int incomingDamage) { return incomingDamage / 2; // Dragon scales - only 50% damage! }
-
Wizard - Conditionally calls
base:public override int Defend(int incomingDamage) { if (_mana >= 5) { _mana -= 5; return (int)(incomingDamage * 0.2); // Magic shield! } return base.Defend(incomingDamage); // Out of mana - use default }
dotnet test --logger "console;verbosity=detailed"Walk through key tests:
| Test | Demonstrates |
|---|---|
CannotCreateAbstractMonster |
Abstract class restriction |
EachMonsterHasUniqueBattleCry |
Abstract method overrides |
GoblinUsesDefaultDefend |
Virtual method - using default |
DragonOverridesDefendWithBetterArmor |
Virtual method - override |
WizardDefendUsesBaseWhenOutOfMana |
Calling base.Method() |
PolymorphismAllMonstersCanAttack |
Polymorphism in action |
Open Arena.cs and show:
public void AddFighter(Monster monster) // Accepts ANY Monster type!
{
Fighters.Add(monster);
}Key insight: The Arena doesn't know or care if it's a Dragon, Goblin, or Wizard. It just knows it's a Monster with an Attack() method.
public int PerformAttack(Monster attacker, Monster defender, ...)
{
int damage = attacker.Attack(); // Polymorphism picks the right override!
...
}dotnet run --project MonsterArena.WebInteractive demo:
- Create one of each monster type
- Show their different battle cries (abstract override)
- Show special abilities (some have them, some don't - virtual override)
- Have monsters attack each other
- Click "Defender Blocks" to show different
Defend()implementations:- Goblin takes 75% damage (default)
- Dragon takes 50% damage (override)
Add a Skeleton.cs that:
- Has 50 health
- Does 8-12 damage
- Says "Rattle rattle..."
- Has NO special ability (use default)
- Takes FULL damage when defending (override to return
incomingDamage)
Add public abstract string GetMonsterType(); to Monster.cs
- Should return "Fire", "Melee", "Magic", etc.
- Update all derived classes
- Add a test that verifies each type
Add public virtual bool CanFly() => false; to Monster.cs
- Override only in
Dragonto returntrue - Display this in the Blazor UI
| Term | Definition | Example in Project |
|---|---|---|
| Abstract Class | A class that cannot be instantiated | Monster |
| Concrete Class | A class that can be instantiated | Dragon, Goblin, Wizard |
| Abstract Method | Method with no body, must be overridden | Attack(), GetBattleCry() |
| Virtual Method | Method with default body, can be overridden | Defend(), SpecialAbility() |
| Override | Replacing a virtual/abstract method in derived class | public override int Attack() |
| Polymorphism | Treating derived objects through base type | List<Monster> holding Dragons and Goblins |
| Base | Keyword to call parent class implementation | base.Defend(damage) |
-
Forgetting
overridekeyword:// WRONG - this creates a NEW method, doesn't override! public int Attack() { ... } // RIGHT public override int Attack() { ... }
-
Trying to instantiate abstract class:
Monster m = new Monster("X", 100); // COMPILE ERROR
-
Not implementing all abstract methods:
- If you inherit from an abstract class, you MUST implement all abstract methods
- Or make your class abstract too
-
Confusing
virtualandabstract:virtual= "here's a default, override if you want"abstract= "no default, you MUST provide implementation"
Enjoy teaching with Monster Arena!