Teaching Strategy: C# Classes

👋 Hi Jordan!

In the first couple years, I taught concepts to students and then coached them as they struggled to understand them. Since then, I've been slowly evolving to a process where I take previous knowledge and help them transfer it to a new concept. I then immediately lead them to reach the limits of their knowledge and then show how the problem can be solved. I'll give you an example.

How It Used To Be Done

Roll back the clock to March of 2016 when I taught my very first C# server-side course at Nashville Software School. When I introduced them to classes and types in C#, I talked about it from several perspectives.

  • Syntax
  • Purpose
  • Real-world modeling in code
  • Power of typing
  • Inheritance from generalities

Then I gave them a variety of exercises to work on so they could practice writing classes, properties, methods, overloading, overriding, etc. It was fairly effective, but I knew that some students just didn't "get it".

How I Do It Now

Orientation to C# starts off with learning the built-in types, and the useful data structures of List and Dictionary. To transfer the existing knowledge they gained about JavaScript, I market them with the following phrases.

  • "List is the new array"
  • "Dictionary is the new object"

After a couple days of working on exercises for those data structures, they are ready to learn about making their own custom types with C# class. That morning, I start them off with a Lightning Exercise (small, fast exercises to warm up their brains). I tell them to create a new project, and then define a Dictionary that represents their favorite car and has the following key/value pairs.

  • Make
  • Model
  • Year
  • Price

I give them about 3-5 minutes to work on that until I hear the popcorn stop popping (i.e. no one is typing any longer). I then live-code my own and answer any questions they have.

Dictionary<string, string> mustang = new Dictionary<string, string>() {
    { "Make", "Ford" },
    { "Model", "Mustang" },
    { "Year", "1981" },
    { "Price", "12,000.49" },
};

Then I ask them to create 2 or 3 more of their other favorite cars and then put them in a List to represent a garage that stores the cars. Five to ten minutes later, I live-code my version and answer questions.

List<Dictionary<string, string>> garage = new List<Dictionary<string, string>>() {
    mustang, thunderbird, fiveHundred, cutlassSupreme
};

The Problem

Ok, lightning exercises done. Then I have a discussion with them about how I'd love to have the Year key store an int instead of a string, and the Price really should be a double type if I want to do some kind of math on the prices of my cars - total value, average value, etc.

List<Dictionary<string, string>> garage = new List<Dictionary<string, string>>() {
    mustang, thunderbird, fiveHundred, cutlassSupreme
};

double totalValue = 0;
garage.ForEach(c => totalValue += c["Price"]);

That attempt to add up the values of my cars immediately throws an exception.

Cannot implicitly convert type 'string' to 'double' (CS0029) [CarLot]

But dang, when I define a Dictionary, I have to specify the type of the key and the type of the value and that forces them all to be the same. The rules of a Dictionary forces the year and price to be string values - there's no way around it.

"What would be nice is if I could create my own version of a Dictionary and specify the type of each property. Make and model as strings, year as integer, and price as double," he said in mock frustration.

The Reveal

This, then leads directly into talking about how C# does allow you to create your own custom types. You can create your own type called Car and specify exactly what it should look like. Then I type the following class into Program.cs right above the Program class. I talk to them later about how each class should be in its own file.

public class Car {
    public string Make { get; set; }
    
    public string Model { get; set; }
    
    public int Year { get; set; }
    
    public double Price { get; set; }
}

Here's what a car should look like in my program! This doesn't make a car, but it certainly defines how one should look. It's a template, or a blueprint for a car. To actually make a car to use in my program, I have to use the new keyword.

public static void Main() {
    Car mustang = new Car() {
        Make = "Ford",
        Model = "Mustang",
        Year = 1981,
        Price = 12000.49,
    }
}

Then I build my other cars as Car types, and then rewrite my garage variable to be a list of cars.

List<Car> garage = new List<Car>() {
    mustang, thunderbird, fiveHundred, cutlassSupreme
}

Look at the benefits already in this initial example of using custom types.

  • There is a lot less to type.
  • A car is now represented accurately in my code.
  • It provides immediate context to another human being reading my code that I have a list of cars.

I keep reminding students that they aren't writing C# for the computer to understand. If that was the goal, we'd still all be writing assembly and using punch cards. We write out code for human beings to understand.

This code does not provide much context other than the names of the variables. There is also a low signal-to-noise ratio.

List<Dictionary<string, string>> garage = new List<Dictionary<string, string>>() {
    mustang, thunderbird, fiveHundred, cutlassSupreme
}

Whereas this code provides much more context to the human brain with a quick scan. I know in a second what the goal of the code is. Also, the signal-to-noise ratio is higher.

List<Car> garage = new List<Car>() {
    mustang, thunderbird, fiveHundred, cutlassSupreme
}

Method Overloading

One of the discussions that organically arose while we were discussing the Car class was how to add behavior to this object - not just properties. I said the most basic of behavior of a car is to drive it, so I created that method.

public void Drive(string soundToMake) {
    Console.WriteLine($"The {Make} {Model} goes {soundToMake}.");
}

Then one of the students asked, "What if I just want all the cars to make the same sound, but have the option to change it if I want?"

I then showed them how to override a method; this is something they could not do in JavaScript, so I talked a bit about that difference. During this discussion, I brought up the term arity and explained what it means and how it makes method overloading possible. Also discussed that this is an example of polymorphism. It is method based polymorphism.

private string _baseSound = "Boink";

public void Drive() {
    Console.WriteLine(_baseSound);
}

public void Drive(string soundToMake) {
    Console.WriteLine(soundToMake);
}

Learning Objectives

  • Custom types
  • Method overloading
  • Arity
  • Polymorphism