Refactoring Myself


A few weeks ago, I opened up an old project from 2010. It was one of those “just-for-fun” apps I built in the quiet hours after work — nothing mission-critical, just code written for the joy of solving a problem. I hadn’t looked at it in over a decade.
Scrolling through the files, I felt like I was reading a letter from my younger self — verbose comments, deeply nested ifstatements (and the cursed else statement that I never use anymore), painstakingly hand-written property getters and setters. There was nothing wrong with the code. It worked. It was clean enough. But it felt… old. Like it belonged to a different era of C# — because it did.
I’ve been writing C# since the early days, and it’s remarkable how much both the language and my style have evolved. C# in 2010 was powerful, but rigid. Expressive, but wordy. Fast-forward to today, and the language is practically unrecognizable in places — leaner, more elegant, and full of concepts that simply didn’t exist back then.
Looking at my own code from that time, I can see more than just outdated syntax — I see a different mindset, a different way of solving problems. This article is a reflection on that evolution. Not just what changed in the language, but whythose changes happened, how they influenced the way we think about code, and what it means to grow alongside the tools we use every day.
If you’ve been with C# for a while, you might see yourself in some of these shifts. If you’re newer to the language, maybe you’ll appreciate just how far it’s come. Either way, come with me — I promise I won’t make you read through a hand-rolled Equals() method.
The Language that Grows Up
Languages, like people, grow up. Sometimes the changes are slow and subtle — a new keyword here, a bit of syntax sugar there. Other times, it’s like running into an old friend who’s now fluent in another language, dresses differently, and goes by a new name. C# has done both.
When I look at the changes in the language over the past decade, it’s clear that many of them weren’t just about convenience. They were about maturity — helping developers express intent more clearly, reduce errors, and write code that’s easier to reason about. And often, these ideas came from outside: other languages, functional paradigms, or simply the hard lessons of experience.
Here are a few milestones where I saw the language really grow up — and where my code grew up with it.
Then, 2010:
var task = Task.Factory.StartNew(() =>
{
var data = DownloadData();
ProcessData(data);
});
task.ContinueWith(t =>
{
if (t.Exception != null)
Log(t.Exception);
});
Now:
try
{
var data = await DownloadDataAsync();
ProcessData(data);
}
catch (Exception ex)
{
Log(ex);
}
Before async and await, writing asynchronous code in C# felt like passing notes in class — fragmented and harder to follow. When C# 5 introduced these keywords, it didn’t just make code shorter. It made it read like synchronous code, which meant fewer bugs, better readability, and less cognitive load.
String.Format (then):
var message = string.Format("Welcome, {0}. You have {1} new messages.", user.Name, user.UnreadCount);
String interpolation (now):
var message = $"Welcome, {user.Name}. You have {user.UnreadCount} new messages.";
It’s a small change, but it reflects a broader shift in the language — a desire to be more expressive, less ceremony. String interpolation, introduced in C# 6, came straight out of the playbook of languages like JavaScript and Python. And once you start using it, going back feels… painful.
Pattern matching (then):
if (shape is Circle)
{
var c = (Circle)shape;
Console.WriteLine($"Radius: {c.Radius}");
}
Now:
if (shape is Circle c)
{
Console.WriteLine($"Radius: {c.Radius}");
}
Now 2.0:
switch (shape)
{
case Circle { Radius: > 10 } bigCircle:
Console.WriteLine($"Big circle with radius {bigCircle.Radius}");
break;
case Circle smallCircle:
Console.WriteLine($"Small circle with radius {smallCircle.Radius}");
break;
case Square s:
Console.WriteLine($"Square with side {s.SideLength}");
break;
}
Pattern matching arrived gradually (starting in C# 7), but it brought with it the ability to describe shapes of data, not just types. It feels like the language is inviting you to write more declarative, readable code — especially as switch expressions and recursive patterns came into play.
Records (embracing immutability then):
public class Person
{
public string FirstName { get; }
public string LastName { get; }
public Person(string first, string last)
{
FirstName = first;
LastName = last;
}
public override bool Equals(object obj) =>
obj is Person p && FirstName == p.FirstName && LastName == p.LastName;
public override int GetHashCode() => HashCode.Combine(FirstName, LastName);
}
Records (now):
public record Person(string FirstName, string LastName);
Records (C# 9) felt like C# finally acknowledging that immutability isn’t a niche idea — it’s foundational. They eliminate boilerplate, encourage safer design, and make intent clear: this is a value type, not an object with hidden mutable state. It was one of the first times I felt C# truly stepping into a more functional mindset.
What my old code reveals about me
When I opened up that 2010 codebase, it wasn’t just the language that looked different — it was me.
There was a time when I thought more code meant more control. I wrote explicit property backing fields for everything. I avoided var because I thought it was too vague. I wrapped every if in braces — even single-liners — because I read a blog post once that said it was “safer.”
But the more interesting thing is why I wrote code that way. I wasn’t trying to be overly cautious or verbose. I was just working with the tools I had. The language encouraged a certain style, and I followed it. I didn’t question much, because the patterns were widely accepted and I was focused on building things, not critiquing the scaffolding.
Yet looking back, I see how much mental overhead I carried. Defensive null checks everywhere. Manual plumbing in constructors. Repetitive equality checks. A reliance on runtime behavior to catch bugs that the compiler now helps me avoid. I was writing code that worked, but not always code that communicated.
Now, with features like records, pattern matching, and nullable annotations, I find myself writing less code — and yet, saying more. My focus has shifted from “how do I make this work?” to “how can I express what this means?”
I don’t regret how I used to write code. That version of me was doing the best he could with what was available. But I am grateful that the language evolved — and that I kept evolving with it.
Evolving with the language
When you’ve been with a language for this long, it starts to feel like a companion. You learn its quirks, you anticipate its patterns, and you grow alongside it. C# today is not the same language I started with — and that’s a good thing.
Languages are living things. They grow to meet new challenges, to support new paradigms, and to help developers write safer, clearer, more expressive code. But the evolution isn’t just in syntax — it’s in how we think. In how we design. In what we value.
Looking back at my old code didn’t just show me how far C# has come — it showed me how far I’ve come. And honestly, I hope that in another ten years, I’ll look at the code I’m writing now and feel exactly the same way.
Because if you’re not a little embarrassed by your old code, you’re probably not growing.
Subscribe to my newsletter
Read articles from Adam Stirtan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
