Syntax sugar we don’t even think about in C#

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class Car
{
private string _brand;
private string _model;
private string _licensePlate;
private int _doors = 4;

public string Brand
{
get
{
return _brand;
}
set
{
_brand = value;
}
}

public string Model
{
get
{
return _model;
}
set
{
_model = value;
}
}

public string LicensePlate
{
get
{
return _licensePlate;
}
set
{
_licensePlate = value;
}
}

public int Doors
{
get
{
return _doors;
}
set
{
_doors = value;
}
}
}

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
2
3
4
5
6
7
8
9
10
public class Car
{
public string Brand { get; set; }

public string Model { get; set; }

public string LicensePlate { get; set; }

public int Doors { get; set; } = 4;
}

Using statement (and declaration)

The using statement ensures that instances implementing IDisposable are properly disposed.

1
2
3
4
using (IDisposable x = CreateSomeDisposable())
{
// do stuff
}

Without the using statement, developers would need to always write a try-finally block and check for null to prevent exceptions.

1
2
3
4
5
6
7
8
9
10
IDisposable x = CreateSomeDisposable();
try
{
// do stuff
}
finally
{
if (!ReferenceEquals(x, null))
x.Dispose();
}

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
2
3
using IDisposable x = CreateSomeDisposable();

// do stuff

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
IEnumerable cars = GetAllCars();
IEnumerator carsEnumerator = cars.GetEnumerator();
try
{
while (carsEnumerator.MoveNext())
{
Car car = (Car)carsEnumerator.Current;

// do stuff
}
}
finally
{
IDisposable disposable = carsEnumerator as IDisposable;
if (!ReferenceEquals(disposable, null))
disposable.Dispose();
}

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
2
3
4
5
IEnumerable<Car> cars = GetAllCars();
foreach (Car car in cars)
{
// do stuff
}

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
2
3
4
5
6
7
8
Car car = new Car();
car.Brand = "Audi";
car.Model = "A8";
car.LicensePlate = "ABCD-1234";
car.Doors = 2;

Car[] cars = new Car[1];
cars[0] = car;

With object and collection initializers the code is more compact and scoped using brackets.

1
2
3
4
5
6
7
8
9
10
11
12
Car car = new Car
{
Brand = "Audi",
Model = "A8",
LicensePlate = "ABCD-1234",
Doors = 2
};

Car[] cars = new Car[]
{
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
2
3
4
5
var person = new
{
GivenName = "John",
Surname = "Doe"
};

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
2
3
4
5
6
7
8
9
10
11
12
var car = new Car
{
Brand = "Audi",
Model = "A8",
LicensePlate = "ABCD-1234",
Doors = 2
};

var cars = 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static IEnumerable<string> Filter(IEnumerable<string> values, FilterString filter)
{
var result = new List<string>();

foreach (var value in values)
{
if(filter(value))
result.Add(value);
}

return result;
}

public delegate bool FilterString(string value);

// usage
var filterValues = Filter(values, delegate(string s)
{
return s.Contains('1');
});

Using lambda expressions and pre-defined delegates, we don’t need to define our own and the code invoking Filter is much compacter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static IEnumerable<string> Filter(IEnumerable<string> values, Func<string, bool> filter)
{
var result = new List<string>();

foreach (var value in values)
{
if(filter(value))
result.Add(value);
}

return result;
}

// usage
var filterValues = Filter(values, s => s.Contains('1'));

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public static class StringExtensions
{
public static int? ToNullableInt(this string value)
{
if(value is null)
return null;
return value.ToInt();
}

public static int ToInt(this string value)
{
return int.Parse(value, NumberStyles.None);
}
}

// usage
var convertedValue = "12345".ToNullableInt();
Without extension methods the code would be more verbose:

public static class StringExtensions
{
public static int? ToNullableInt(string value)
{
if(value is null)
return null;
return ToInt(value);
}

public static int ToInt(string value)
{
return int.Parse(value, NumberStyles.None);
}
}

// usage
var convertedValue = StringExtensions.ToNullableInt("12345");

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class CarBrandModelGroup : IEquatable<CarBrandModelGroup>
{
public CarBrandModelGroup(
string brand,
string model
)
{
Brand = brand;
Model = model;
}

public string Brand { get; }

public string Model { get; }

public bool Equals(CarBrandModelGroup other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Brand == other.Brand && Model == other.Model;
}

public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return obj.GetType() == GetType() && Equals((CarBrandModelGroup)obj);
}

public override int GetHashCode()
{
uint hc1;
if (ReferenceEquals(Brand, null))
hc1 = 0;
else
{
hc1 = (uint)Brand.GetHashCode();
}

uint hc2;
if (ReferenceEquals(Model, null))
hc2 = 0;
else
{
hc2 = (uint)Model.GetHashCode();
}

return (int)(hc1 ^ hc2);
}
}

// the group by brand and model cars with doors == 2
var carBrandModelsWithTwoDoors = new HashSet<CarBrandModelGroup>();
foreach (var car in cars)
{
if (car.Doors == 2)
{
carBrandModelsWithTwoDoors.Add(new CarBrandModelGroup(
car.Brand,
car.Model
));
}
}

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
2
3
4
5
var carBrandModelsWithTwoDoors = cars.Where(c => c.Doors == 2).DistinctBy(c => new
{
c.Brand,
c.Model
}).ToArray();

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!