Object-Oriented Programming in C#: From Theory to Production Code
Modern C# practices, real-world examples, and the design lessons every developer should know. I first wrote this post in 2013. I have shippe...
Modern C# practices, real-world examples, and the design lessons every developer should know.
I first wrote this post in 2013. I have shipped a lot of C# since then, and rereading it I found two kinds of problem: code that would not survive contact with a real program, and advice that was true for Java rather than for C#. Both are fixed below. I have also stopped pretending that object orientation is a set of four words you memorise for an interview.
So this is the same journey, rebuilt: what an object actually is, why the philosophy matters more than the syntax, and how each idea looks in C# in 2026 rather than C# in 2008. The examples are the ones I have always used, because they still work.
Everything can be an object, and that is the least useful thing about it
Look around you. Computer, chair, table, pen. All objects, because each one has attributes and activities. A computer has a processor, RAM, a keyboard. It runs programs, displays information, accepts requests. Attributes and behaviour, bundled.
Fine. Now the part that took me years to actually understand.
An object's data and responsibilities depend entirely on the problem domain. Ask two engineers to model a "customer" and you will get two different answers. One gives you name, email, phone, and the ability to purchase and bargain. The other gives you first name, middle name, last name, gender, credit card, and the ability to open an account. Neither is wrong. One was thinking about a grocery store and the other about a bank.
This is why "is Name an attribute or an object?" has no answer in the abstract. In a banking system, Name is an attribute of Customer. In a business that sells names for newborn babies, Name is an object, because now it has meaning, length, origin, and popularity.
The lesson I would give my 2013 self: you are not modelling reality. You are modelling a problem. Reality is infinite and your program is not. Every attribute you add is a claim that your problem cares about it. Most of the bad class designs I have reviewed came from someone modelling the world instead of the requirement.
A class is a description, an object is an instance
A class describes objects that share attributes and behaviour. It is the blueprint. If Nashra, Maksura and Misha are students, then Student is the class and those three are objects.
Take a real requirement: "the user provides a first, middle and last name, and wants the full name and the reversed full name."
Read it and pull out the noun. There is one object here:
- Person: has a first name, a middle name, a last name. Can tell you its full name. Can tell you its reversed name.
In 2013 I wrote it like this, and I want to keep it visible because it is what most people write first:
namespace OOPWalkThrough1
{
class Person
{
// Public fields. We will regret this in about four paragraphs.
public string firstName;
public string middleName;
public string lastName;
public string GetMyFullName()
{
return firstName + " " + middleName + " " + lastName;
}
public string GetMyReverseName()
{
string reverseName = "";
string fullName = GetMyFullName();
for (int index = fullName.Length - 1; index >= 0; index--)
{
reverseName += fullName[index];
}
return reverseName;
}
}
}
The data is represented by fields, the responsibilities by methods. Any object created from this class represents our domain Person. That much was right.
Two things were not. The public fields break encapsulation, which I admitted at the time and we will fix shortly. And GetMyReverseName builds a string by repeated concatenation inside a loop. Strings are immutable in .NET, so that loop allocates a brand new string on every single iteration. For a name it does not matter. For anything long it is quadratic, and I have profiled production code where exactly this pattern was the bottleneck.
Here is the same class as I would write it today:
namespace OopWalkthrough;
public sealed class Person
{
// Auto-properties: the compiler writes the backing field.
// Private set means the value is fixed once the object exists.
public string FirstName { get; }
public string MiddleName { get; }
public string LastName { get; }
public Person(string firstName, string middleName, string lastName)
{
// Validate at the boundary. An object should not be constructible
// in a state it cannot honour.
FirstName = firstName ?? throw new ArgumentNullException(nameof(firstName));
MiddleName = middleName ?? string.Empty;
LastName = lastName ?? throw new ArgumentNullException(nameof(lastName));
}
// A computed value with no side effects is a property, not a method.
public string FullName => string.Join(' ', new[] { FirstName, MiddleName, LastName }
.Where(part => !string.IsNullOrWhiteSpace(part)));
// One allocation instead of one per character.
public string ReverseName
{
get
{
var chars = FullName.ToCharArray();
Array.Reverse(chars);
return new string(chars);
}
}
}
Note sealed. My 2013 self left every class open to inheritance by default, because that felt generous. It is not generous, it is a promise. Sealing by default and opening deliberately is the habit I wish I had started with.
Separate the logic from the interface
This is the section that has aged best, so I am keeping the argument intact.
Most of us write a calculator like this the first time:
public partial class CalculatorUI : Form
{
private void addButton_Click(object sender, EventArgs e)
{
double firstNumber = Convert.ToDouble(firstNumberTextBox.Text);
double secondNumber = Convert.ToDouble(secondNumberTextBox.Text);
double result = (firstNumber + secondNumber);
resultTextBox.Text = result.ToString();
}
private void subtractButton_Click(object sender, EventArgs e)
{
double firstNumber = Convert.ToDouble(firstNumberTextBox.Text);
double secondNumber = Convert.ToDouble(secondNumberTextBox.Text);
double result = (firstNumber - secondNumber);
resultTextBox.Text = result.ToString();
}
// ... and two more that are identical apart from one character
}
Now change it to a web application. Delete CalculatorUI.cs and your arithmetic goes with it, because the arithmetic never existed anywhere else. Every operator lives inside a button click handler. You would rewrite all of it. Scale that thought to an enterprise application and you can see the shape of a very bad quarter.
There is a second problem that is worse in the long run. If you want to add numbers somewhere else in the program, you rewrite the logic there. Now the same rule lives in two places, and one day someone fixes one of them.
So what did I actually do wrong? I mixed everything into CalculatorUI. A UI is responsible for displaying information and collecting it from the user. That is the whole job. I gave it a second job it was never suited for.
Refactor it. Take baby steps rather than a grand rewrite, because a grand rewrite is how refactors die. The four arithmetic operations suggest an object:
public class Calculator
{
public double Add(double firstNumber, double secondNumber) => firstNumber + secondNumber;
public double Subtract(double firstNumber, double secondNumber) => firstNumber - secondNumber;
public double Multiply(double firstNumber, double secondNumber) => firstNumber * secondNumber;
public double Divide(double firstNumber, double secondNumber) => firstNumber / secondNumber;
}
And the UI shrinks to what it should always have been:
public partial class CalculatorUI : Form
{
private readonly Calculator _calculator = new();
private void addButton_Click(object sender, EventArgs e)
{
var first = Convert.ToDouble(firstNumberTextBox.Text);
var second = Convert.ToDouble(secondNumberTextBox.Text);
resultTextBox.Text = _calculator.Add(first, second).ToString();
}
}
Change to a web app now and you delete CalculatorUI only. Calculator is untouched. Need arithmetic elsewhere? Create the object and call it. The logic exists in exactly one place, which means it can be fixed in exactly one place.
Thirteen years later I would add one thing my original left out. Divide has a bug hiding in plain sight. It is a design decision disguised as a missing check: what should happen when secondNumber is zero? Doubles answer Infinity rather than throwing, so the UI cheerfully prints ∞. Deciding that is the calculator's job, not the button's. That is the real lesson of this section: when logic moves out of the UI, the questions it was hiding become visible.
Data hiding, constructors and properties
Data hiding restricts direct access to data. You reach it through a safe mechanism instead. A motorbike is the example I keep coming back to: you have no access to the piston. You press the start button, and the button controls the piston. If the manufacturer exposed the piston directly, controlling what happens to it would be impossible.
Which brings us back to the crime scene:
class Person
{
public string firstName; // anyone can write anything here
public string middleName;
public string lastName;
}
Make them private, and now nothing outside can touch them. So how does the UI read a name? In 2013 I wrote getters and setters:
public void SetFirstName(string firstName)
{
this.firstName = firstName;
}
public string GetFirstName()
{
return firstName;
}
Small correction to my old post, and it is embarrassing: it then showed personObject.GetFirstName("Nashra"); to set a value, and string firstName = personObject.GetFirstName; to read one. The first calls the getter with an argument. The second forgets the parentheses. Neither compiles. If you copied them and they failed, that was my fault, not yours.
The correct calls are:
personObject.SetFirstName("Nashra");
string firstName = personObject.GetFirstName();
But do not write this at all. Three attributes means six methods, and the class becomes noise. There is a better reason to avoid them than the typing: a method represents a responsibility. "GetFirstName" is not something a Person does. It is something you do to a Person. Modelling data access as behaviour makes the object look busier than it is. Use a property:
public class Person
{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
// Combinational data belongs in a get-only property, not a method.
public string FullName => $"{FirstName} {MiddleName} {LastName}";
}
The rules I still use, unchanged since 2013 because they were right:
- If client code does not need the data, do not write a property for it. Keep it private.
- If the data should be read but not written, write
getand noset. - Computed values get a property with only a
get.FullName, notGetMyFullName().
Modern C# adds one worth knowing. init lets a property be set during construction and never again, which gives you an immutable object without a constructor full of parameters:
public class Person
{
public required string FirstName { get; init; } // required: C# 11
public string? MiddleName { get; init; }
public required string LastName { get; init; }
}
var person = new Person { FirstName = "Nashra", LastName = "Maksura" };
// person.FirstName = "other"; // compile error: init-only
And if the type is nothing but data, a record says so in one line and gives you value equality for free:
public record Person(string FirstName, string? MiddleName, string LastName)
{
public string FullName => $"{FirstName} {MiddleName} {LastName}";
}
Constructors
A constructor initialises an object when it is created, or does some initial work:
public Person(string firstName, string middleName, string lastName)
{
this.firstName = firstName;
this.middleName = middleName;
this.lastName = lastName;
}
var personObject = new Person("Nashra", "Misha", "Maksura");
The point that deserves more weight than I gave it: a constructor is a contract that says what an object needs in order to exist. If Person cannot function without a first name, the constructor should demand one. Every optional constructor parameter is a state your class has promised to handle.
Overloading
Overloading allows several methods to share a name and differ by parameters. The bike again: Start has two forms, auto start and kick start. Same intent, different inputs.
Suppose the calculator must add three numbers. You could write AddThreeNumber, then rename the original to AddTwoNumber. Now your client sees five methods for four operations, and two of them do the same job with different arity. The object looks more complicated than it is.
public double Add(double firstNumber, double secondNumber); public double Add(double firstNumber, double secondNumber, double thirdNumber);
Your client sees one Add. Similar responsibilities should share a name. The corollary matters more, and I did not say it in 2013: if two overloads do genuinely different things, giving them the same name is a lie. Overloading is for the same idea with different inputs, not for saving a word.
Constructor overloading follows the same rule, and constructors can chain so the logic lives once:
public Person() { }
public Person(string firstName, string lastName)
{
this.firstName = firstName;
this.lastName = lastName; // note: the field, not the property
}
// ": this(...)" reuses the two-argument constructor instead of copying it
public Person(string firstName, string middleName, string lastName)
: this(firstName, lastName)
{
this.middleName = middleName;
}
One more correction. My original wrote this.LastName = lastName; in that middle constructor, mixing the property LastName with the field lastName. It happened to work only if a matching property existed. The convention that prevents this entire category of confusion: fields _camelCase, properties PascalCase, and then the two can never be mistaken for each other.
Static members, namespaces and access modifiers
Some data and behaviour belongs to the class rather than to any instance of it. That is what static means.
Consider a Trainee:
class Trainee
{
public string name;
public string id;
public string email;
}
Name, id and email belong to each trainee. Now ask whether "number of trainees" is an attribute of a trainee object. It is not. No individual trainee has a number-of-trainees. The class does:
class Trainee
{
public string name;
public string id;
public string email;
// Belongs to the class, not to any one trainee.
public static int numberOfTrainees;
}
Trainee.numberOfTrainees = 30;
int count = Trainee.numberOfTrainees; // through the class, never through an object
The test I use: if the value would be the same no matter which instance you asked, it does not belong to the instance.
Now apply it to Calculator. Does it have data of its own? No. Every method takes its inputs as parameters and returns a result. It is a utility, so the methods can be static and callers never construct one:
public static class Calculator
{
public static double Add(double firstNumber, double secondNumber) => firstNumber + secondNumber;
public static double Subtract(double firstNumber, double secondNumber) => firstNumber - secondNumber;
public static double Multiply(double firstNumber, double secondNumber) => firstNumber * secondNumber;
public static double Divide(double firstNumber, double secondNumber) => firstNumber / secondNumber;
}
double result = Calculator.Subtract(firstNumber, secondNumber);
A static class forces every member to be static and cannot be instantiated.
An honest warning I owe you, having watched it happen. Static is convenient, and convenience is how it spreads. The moment a static class holds mutable state, you have global state: two tests interfere, two threads race, and nothing can be substituted for a fake. Static is right for Calculator precisely because it holds nothing. Add one static field and reconsider the whole design.
Static constructors
class Trainee
{
static Trainee()
{
numberOfTrainees = 30;
}
public static int numberOfTrainees;
}
You cannot call a static constructor. The runtime runs it once, before the first instance is created or any static member is touched. It takes no access modifier and cannot be overloaded.
My old post asked "is it possible to use non-static members from static members?" and never answered. The answer is no, and the reason is worth more than the rule: a static member has no instance. There is no this for it to reach through. The question is not "is it allowed", it is "which object would it even mean?"
Namespaces
Namespaces are a logical grouping. They organise your application internally and prevent name collisions with other code. The .NET root namespace is System:
| Namespace | Purpose |
|---|---|
System.Windows.Forms | Classes for building Windows Forms applications |
System.IO | Reading and writing data to files |
System.Data | Data access |
System.Web | Web Forms applications |
A using directive tells the compiler where to look. Modern C# adds file-scoped namespaces, which remove one level of indentation from every file you will ever write:
namespace Contoso.Banking; // no braces, applies to the whole file
public class Account { }
Access modifiers
| Modifier | Access is limited to |
|---|---|
public | Nothing. Any other class can reach it. |
private | The containing type only. |
internal | The same assembly. |
protected | The containing class and types derived from it. |
protected internal | The same assembly, or derived classes in any assembly. |
private protected | Derived classes, but only within the same assembly. Added in C# 7.2. |
Default to private and widen only when something outside genuinely needs it. Every public member is a promise you have to keep.
Inheritance
Inheritance lets you build a new class from an existing one and add to it. A bike manufacturer reuses the mechanism of last year's model and adds features, rather than starting from nothing.
In a banking domain you find Branch, Customer, Loan, all distinct. You also find Savings Account and Checking Account, which are not distinct at all. Both have an account number, a customer, a withdraw and a deposit. Savings has an interest amount. Checking has a service charge and a transaction count.
That shared part is an IS-A relationship, and IS-A is what inheritance means. Start with the base:
public class Account
{
private string accountNumber;
private double balance;
public bool Deposit(double amount)
{
balance += amount;
return true;
}
public bool Withdraw(double amount)
{
balance -= amount;
return true;
}
}
Then the subclass:
public class SavingsAccount : Account
{
private double interestAmount;
public double InterestAmount
{
get { return interestAmount; }
set { interestAmount = value; }
}
}
SavingsAccount now has everything public and protected that Account has, plus its own interest amount. It does not get accountNumber and balance, because those are private. Private means private to the containing type, and a subclass is a different type. This surprises people constantly.
So expose them from the base, and note that balance is read-only because only a transaction may change it:
public string AccountNumber
{
get { return accountNumber; }
set { accountNumber = value; }
}
public double Balance
{
get { return balance; } // no setter: changed by Deposit/Withdraw only
}
C# does not support multiple inheritance of classes. A class has exactly one base. Interfaces are how you get multiple contracts, and we will come to them.
Polymorphism
Polymorphism means the ability to take more than one form: the same operation behaving differently depending on the object. Overloading is one form of it. Overriding is the other, and it is the one that earns the name dynamic polymorphism, because the decision is made at run time.
Method overriding, and the bug in my original post
Change the requirement. Savings must keep a minimum balance of 1000. Checking may go to zero. Withdraw already lives in the base class, so how do we give Savings its own rule?
Mark the base method virtual, which means "a subclass may replace this":
public virtual bool Withdraw(double amount)
{
balance -= amount;
return true;
}
Then override it. And here is where my 2013 post was wrong in a way that would take your program down. It published this:
public override bool Withdraw(double amount)
{
if (Balance - amount >= 1000)
{
return Withdraw(amount); // BUG: this calls ITSELF, forever
}
return false;
}
The comment beside it said "use base class Withdraw method if condition is fulfilled". It does not. Inside an override, Withdraw(amount) resolves to the most derived implementation, which is the method you are already standing in. It calls itself, which calls itself, until the stack runs out and the process dies with a StackOverflowException that you cannot even catch in .NET.
The keyword you need is base. It means "the implementation I inherited", and it is the only way to reach past your own override:
public override bool Withdraw(double amount)
{
if (Balance - amount >= MinimumBalance)
{
return base.Withdraw(amount); // the inherited implementation
}
return false;
}
I am leaving the broken version visible rather than quietly correcting it, because the failure is instructive. It compiles. It passes review. It looks like it is delegating. It only fails when someone withdraws money, which is to say, in production. Infinite recursion through an override is one of the classic ways inheritance bites, and it bites precisely because the code reads as though it is doing the right thing.
To override, the signature must match the base exactly.
Using base constructors
Savings needs an account number and an interest rate. The direct version works:
public SavingsAccount(string accountNo, double interestRate)
{
AccountNo = accountNo; // via the base property
this.interestRate = interestRate;
}
But the base should initialise its own data. Give Account a constructor:
public Account(string accountNo)
{
this.accountNo = accountNo;
}
And chain to it:
public SavingsAccount(string accountNo, double interestRate)
: base(accountNo)
{
this.interestRate = interestRate;
}
Savings initialises only what it owns. The base initialises what it owns. That is the division of labour inheritance is supposed to give you.
Now a trap that catches everyone once. Add a parameterless constructor to Savings and Checking and the build breaks:
public SavingsAccount() { } // compile error
The reason is not obvious from the message. A constructor that does not chain explicitly calls base() implicitly. Account used to have a parameterless constructor, but writing any constructor destroys the compiler-generated default one. So the implicit base() now refers to something that no longer exists. Give the base one back:
public Account() { }
Up-casting and down-casting
A subclass object can be held in a base class reference:
Account mySavingsAccount = new SavingsAccount();
That is up-casting. Two questions follow, and the answers are the heart of polymorphism.
Can you reach InterestRate through mySavingsAccount? No. The reference type is Account, so you see only what Account declares. The interest rate is there in memory; you just have no way to name it.
Which Withdraw runs, Savings or Account? Savings. This is the important one. The reference decides what you can call. The object decides what actually runs. That gap is the whole mechanism: your code speaks to an Account and the correct behaviour executes anyway.
Down-casting goes the other way, and it can fail:
// Old and dangerous: throws InvalidCastException if it isn't a SavingsAccount
var savings = (SavingsAccount)mySavingsAccount;
// Better: pattern matching. Asks and acts in one step.
if (mySavingsAccount is SavingsAccount s)
{
Console.WriteLine(s.InterestRate);
}
One thing I would tell my younger self: if you find yourself down-casting often, the polymorphism is not doing its job. Every cast is you telling the compiler that you know better than the type system, and that is a bet you will eventually lose. The usual fix is a virtual method on the base, so the object answers instead of you asking what it is.
Encapsulation
Encapsulation means keeping behaviour and data together in one unit. The bike has actions (light, horn) and attributes (colour, weight), bundled into one thing. In code, methods and properties live together in an object.
The advantage is that the implementation is not accessible to the client. The user needs to know what the unit does and what to give it, and nothing else.
The one line to remember: encapsulation is not "make fields private and add properties". That is the mechanic, not the idea. A class with a private field and a public get/set pair for it has encapsulated nothing at all; it has just made the field's exposure take three lines instead of one. Encapsulation is when the object protects an invariant. Balance is a good example. It is get-only, and it changes only through Deposit and Withdraw, so the object can guarantee that money never appears from nowhere. That guarantee is the encapsulation. The property is just how it is spelled.
Interfaces
An interface is a contract. It states what to do, not how to do it. It declares the members an implementer will provide, and says nothing about how they work. Different classes can implement the same interface in dramatically different ways.
A Car might implement IDrivable. So might Truck, Aircraft, Train and Boat. The Driver has no idea which one it is holding. It knows the interface:
public interface IDrivable
{
void GoForward(int speed);
}
public class Truck : IDrivable
{
public void GoForward(int speed) { /* ... */ }
}
public class Aircraft : IDrivable
{
public void GoForward(int speed) { /* ... */ }
}
Now, the part I have to correct. My 2013 post listed rules for interfaces that were Java's rules, not C#'s. I had been reading Java material and did not notice the language switch. If you learned any of these from me, please unlearn them:
- "Each variable declared in an interface must be assigned a constant value": wrong. A C# interface cannot declare fields at all. It declares methods, properties, events and indexers.
- "Every interface variable is implicitly public, static and final": that is Java. C# has no
finalkeyword. - "If a class implementing an interface does not implement all its methods, the class becomes an abstract class": that is Java again. In C# it is simply a compile error unless you explicitly declare the class
abstract.
What is true in C#: members are implicitly public and cannot carry an access modifier; an interface may inherit other interfaces; and both classes and structs may implement any number of them. That last point is how C# gives you multiple inheritance of contract while refusing multiple inheritance of implementation, which is the ambiguity it was avoiding all along.
And one genuine change since I wrote the original. Since C# 8, an interface can carry a default implementation:
public interface IDrivable
{
void GoForward(int speed);
// Default implementation: implementers get this free, and may replace it.
void Halt() => GoForward(0);
}
This exists mainly so a library can add a member to a published interface without breaking every implementer on earth. It is a versioning tool. Reaching for it as a way to share logic between classes usually means you wanted an abstract base class.
Abstract classes
An abstract class cannot be instantiated. It exists to be inherited. Usually one or more of its members is abstract, meaning declared but not implemented, which forces derived classes to supply the behaviour. A class can be abstract with no abstract members at all, and that is still useful: it says "this is a base, not a thing".
public abstract class Account
{
private double balance;
private string accountNo;
public Account() { balance = 0; }
public Account(string accountNo) { this.accountNo = accountNo; }
public double Balance => balance;
public string AccountNo => accountNo;
public virtual bool Deposit(double amount)
{
balance += amount;
return true;
}
public virtual bool Withdraw(double amount)
{
balance -= amount;
return true;
}
}
Here the class is abstract but nothing inside it is. The only difference from the non-abstract version is this:
Account accountObj = new Account(); // compile error: cannot instantiate Account accountObj = new SavingsAccount(); // fine: up-casting still works
That is not a small difference. "Account" on its own is not a thing a bank has. It has savings accounts and checking accounts. Making the base abstract encodes that fact in the type system rather than in a comment.
Add an abstract member and you place a requirement on every subclass:
public abstract class Account
{
private double balance;
// No body. Every concrete subclass MUST answer this.
public abstract double GetYearlyDeductionAmount();
public double Balance => balance;
}
Savings and Checking must now implement GetYearlyDeductionAmount with their own logic. This is design guidance enforced by the compiler. When a Loan account arrives next year, the compiler tells the author exactly what they owe.
Interface or abstract class?
| Interface | Abstract class | |
|---|---|---|
| Multiple inheritance | A class may implement many. | A class may extend only one. |
| Default implementation | Since C# 8, yes, but intended for versioning rather than code sharing. | Full code, default code, or abstract stubs that must be overridden. |
| State | No fields. Cannot hold state. | Fields, constructors and instance state, all available. |
| Homogeneity | Best when all the implementations share is a set of signatures. | Best when implementations are all of a kind and share status and behaviour. |
| Adding a member | Every implementer must supply it, unless you give it a default. | Provide a default implementation and existing code keeps working. |
The way I actually decide, after years of getting it wrong in both directions:
Ask whether you are describing a capability or a kind. IDrivable is a capability. A Truck and an Aircraft have nothing in common except that both go forward, so an interface is right. Account is a kind. Savings and Checking share a balance, an account number and the meaning of a deposit, so an abstract class is right, because there is real state and real shared behaviour to inherit.
And when both fit, prefer the interface. It costs you the single inheritance slot you may need later, and it is the thing your tests can substitute.
What I would tell my 2013 self
The four words are the easy part. Encapsulation, inheritance, polymorphism, abstraction: you can memorise them in an afternoon and still design badly for years, which is roughly what I did.
The parts that took longer:
- Model the problem, not the world. Every attribute is a claim that your problem cares about it.
- Inheritance is the most over-used idea here. It couples a subclass to a base forever. Reach for it when there is a genuine IS-A and genuine shared state. Otherwise compose.
- Encapsulation is about protecting invariants, not about the ratio of private fields to public properties.
- Seal by default. Openness is a promise, and promises have to be kept.
- Check which language your rule came from. I published Java's interface rules under a C# heading and did not notice for years.
The examples in this post are the same ones I used thirteen years ago. They still teach the ideas, and the ideas have not changed. The code around them has, quite a lot, and it is better now, mostly because I have been wrong often enough to notice the patterns.
Thank you for staying with me. Best of luck.
