When I finished my university degree, back in November 2010 (yes, I’m getting old), Microsoft wasn’t as open as it is today, but because they had a lot of education protocols we had access to a lot of their development tools for free — Visual Studio, SQL Server, Team Foundation Server, just to name a few.
Because of this, C# was actually the language used to teach Object-Oriented Programming (OOP), design patterns and even deeper runtime concepts like garbage collection (an ode to the amazing book “CLR via C#” from Jeffrey Richter — I learned so much).
At the time, we also used a lot of Java, C, C++, LISP, and even Assembly, but C# earned a place in my heart.
Every time I had to create something from scratch that could be run on Windows, C# and .NET Framework was always my first choice. I’m not sure if it was the language or how good Visual Studio was as an IDE compared to Eclipse or NetBeans, but I always found C# to be the most productive programming language I ever used.
And this was between 2006 and 2010, a period were Microsoft released C# versions 3.0 and 4.0 which, in my opinion, completely overthrown their direct competition — Java.
We had auto-implemented properties, extension methods, LINQ, implicit typed variables, anonymous types, query and lambda expressions, just to name a few.
This changes aimed to keep developers focused in implementing business requirements instead of writing boilerplate code. Some may disagree, but for me it was the start of a new era in terms of programming productivity.
If we look back, C# as come a long way, introducing new features every version and I think we can all agree C# 11.0 is an extremely mature and powerful language, suitable both for enterprise or simple applications.
Everyone now knows how much I love everything about C# — almost :) — and since we are starting a new year, I’m feeling nostalgic and I think it is a good time to show how far we’ve come and how much syntax sugar we use nowadays without thinking how much time it really saves us.
Auto-Properties (with initializers)
Ever since C# 1.0 we have properties, which behave like fields from a class or struct, but they are a special case of methods that can implement custom logic, not just get or set the field value.
If we were to create a class Car
in C# 1.0 to hold some information like brand, model, license plate and even the number of doors, with a default of 4, it would look like this:
1 | public class Car |
With the introduction of auto-properties in C# 3.0 and initializers in C# 6.0, we can make the code much more compact and readable.
1 | public class Car |
Using statement (and declaration)
The using statement ensures that instances implementing IDisposable
are properly disposed.
1 | using (IDisposable x = CreateSomeDisposable()) |
Without the using
statement, developers would need to always write a try-finally
block and check for null
to prevent exceptions.
1 | IDisposable x = CreateSomeDisposable(); |
With the release of C# 8.0 we can now use the using
declaration, which simplifies even more the code, removing the need for brackets, disposing the instance at the end of the current scope.
1 | using IDisposable x = CreateSomeDisposable(); |
Foreach loop
The foreach
keyword makes it easier to iterate over an IEnumerable
but did you know that, before C# 1.2, developers had to iterate over the enumerator and explicitly call the dispose method?
1 | IEnumerable cars = GetAllCars(); |
With the release of C# 2.0, Microsoft introduced generics and iterators as a first-class part of the language which nowadays means that iterating over any collection is just a simple use of the foreach
keyword:
1 | IEnumerable<Car> cars = GetAllCars(); |
Object and collection initializers
Before C# 3.0, when initializing a new object you had to invoke the constructor followed by lines of assignment statements. Same with collections, were method Add
or indexers were used.
1 | Car car = new Car(); |
With object and collection initializers the code is more compact and scoped using brackets.
1 | Car car = new Car |
Implicitly typed variables (and anonymous types)
The release of C# 3.0 introduced some features, being one the possibility of creating anonymous types which are usually used to store read-only data into a temporary instance, being the class generated internally by the compiler.
Because developers don’t know the class name since it will be generated at compile time, Microsoft had to provide a way to define the variable type, so they created the var
keyword that will tell the compiler it should infer the type.
1 | var person = new |
Because var
can be used with anything, not just anonymous classes, some developers disagree it improves code legibility despite reducing the code that must be written, specially when defining generic variables.
I personally believe that var
is an improvement to code legibility as long as the surrounding code is properly implemented, like giving proper names to variables and methods.
1 | var car = new Car |
Lambda expressions (and Action/Func)
To create anonymous functions that can be implicitly converted to delegates as long as the signature matches, Microsoft added the lambda declaration operator =>
and provided into the framework pre-existing delegate types like Action
for void results, or Func<T>
if a result was returned, with multiple overloads for variable input parameters.
Before C# 3.0 developers had to define their own delegate classes and invokers had to be explicit by using the delegate keyword.
1 | static IEnumerable<string> Filter(IEnumerable<string> values, FilterString filter) |
Using lambda expressions and pre-defined delegates, we don’t need to define our own and the code invoking Filter
is much compacter.
1 | static IEnumerable<string> Filter(IEnumerable<string> values, Func<string, bool> filter) |
Extension methods
The release of C# 3.0 also introduced extension methods, an extremely powerful feature that allows to “add” methods to existing classes without modifying, extending or recompiling the class. They look like instance methods but are actually static methods that receive the instance as the first parameter and the compiler does the magic for us.
1 | public static class StringExtensions |
LINQ — Language Integrated Query
I want to give a special note about LINQ, despite not being an integral part of the C# language but a framework library, it is a powerful demonstration of what can be done when using a lot of C# syntactic sugar features — the usage of generics, iterators, extension methods over IEnumerable
, lambda expressions, anonymous types and implicit typed local variables.
Before C# 3.0, if we wanted to filter a collection cars with two doors and store a distinct collection of brands and models, we had to implement a class with an equality overload and use a HashSet to prevent duplicates.
1 | public class CarBrandModelGroup : IEquatable<CarBrandModelGroup> |
With the introduction of LINQ, the code will be a simple use of the available methods while keeping in mind anonymous classes implement the default comparer, so no need for a custom class for grouping.
1 | var carBrandModelsWithTwoDoors = cars.Where(c => c.Doors == 2).DistinctBy(c => new |
Conclusion
In this article I’ve shown how C# have evolved over the years to be a more productive and developer friendly language.
Funny enough, when I started to write this article I wanted to cover major features that have been added over the years but then I realized just how big this language is and a single article would be too much, so I ended focusing more on C# 3.0.
This clearly calls for more!