DRY Code in C#

When several classes repeat the same properties and methods, the code breaks the DRY rule (Don't Repeat Yourself). One class that does everything isn't the answer either.

Novice · C# · OOP · Updated

After this lesson you can

  • Spot code that repeats itself across classes, and explain why one class for everything is no fix

Key takeaways

  • DRY means Don't Repeat Yourself. Code repeated across classes has to be changed in every copy, and missing one is a bug.
  • Merging every kind into one class with a Type property is no fix: objects carry properties they never use, and every method fills up with ifs.
  • A good class is cohesive: it has one clear job. One Character class that is every kind of character at once has four.

With encapsulation, you group related data and methods into classes, each with a clear job. But as you write more classes, you'll notice some of them look very alike: the same properties, the same methods, sometimes the same code inside them.

When that happens, your code breaks the DRY rule: Don't Repeat Yourself.

An adventure game

Say you're writing an adventure game with three kinds of character: an archer, a wizard and a knight. Here's how the classes look:

Character, Archer, Wizard and Knight classes, with most properties and methods in common

(Class diagrams like these usually write names in lower case: levelUp() here is LevelUp() in C#.)

Every character has a name, health, a level and experience. Every character can attack, take damage and level up. The parts in red belong to one kind only: an archer's arrows, a wizard's mana, a knight's armor. The methods in blue work a little differently for that kind of character.

In code, the repetition looks like this:

Archer archer = new Archer { Name = "Robin", Health = 100 };
Wizard wizard = new Wizard { Name = "Merlin", Health = 80 };

archer.LevelUp();
wizard.LevelUp();
Console.WriteLine(quot;{archer.Name}: level {archer.Level}, health {archer.Health}");
Console.WriteLine(quot;{wizard.Name}: level {wizard.Level}, health {wizard.Health}");

public class Archer
{
    public string Name { get; set; } = "";
    public int Health { get; set; }
    public int Level { get; set; } = 1;
    public int Arrows { get; set; }

    public void LevelUp()
    {
        Level++;
        Health += 10;
    }
}

public class Wizard
{
    public string Name { get; set; } = "";
    public int Health { get; set; }
    public int Level { get; set; } = 1;
    public int Mana { get; set; }

    public void LevelUp()
    {
        Level++;
        Health += 10;
    }
}
Robin: level 2, health 110
Merlin: level 2, health 90

Two classes, and already Name, Health, Level and LevelUp are written twice. Add a knight and they're written three times. Now imagine leveling up should add 15 health instead of 10: you'd have to find and change every copy, and missing one is a bug.

One class for everything?

One way to remove the repetition is to merge every kind of character into one class:

One Character class with a type, plus arrows, mana and armor for every character

  • A Type property says which kind of character it is.
  • Arrows is only used by archers, Mana only by wizards, Armor only by knights.
  • Methods that differ by kind need if statements inside: if the type is archer, do this; if it's a wizard, do that.

The repetition is gone, but this is a bad solution:

  • Every character carries properties it never uses. A knight has arrows and mana, always zero.
  • Every method is a tangle of ifs. Adding a fourth kind of character means changing every one of them.
  • The class isn't cohesive any more. It no longer has one clear job; it has four.

The next lessons show the right tool: inheritance, which puts the shared parts in one class and keeps each kind's own parts in its own class.

Check yourself

1 question to try on the lesson page.

So what

Repeated code means repeated bugs, and one giant class means tangled code. Spotting both is the first step toward inheritance, which fixes the repetition without the tangle.

Key terms

Cheat sheets