Ben Bowen's Blog • Home / Blog • • About • • Subscribe •

Two Decades of C#: A Reference - C# 5 and C# 6 

Since C# was introduced in 2000 the language has grown immensely in size and I'm not sure it's possible for any one person to have intimate knowledge of every language feature in their head at all times. Therefore I wanted to write a series of quick reference posts summarizing all the major new language features ever since C# 2.0. I won't go in to detail with any of them, but instead I want this series to act as a reference for myself (and hopefully you too!) that I can go back to from time-to-time to remember what tools I have in the toolbox. :)

One small note before we start: I'm going to skip some of the more fundamental stuff (for example C# 2.0 introduced generics, but they're so widespread in usage that they're not really worth including); and I may also 'glue' a few features together for the sake of making this more terse. This series isn't meant to be a definitive or historical record of the language; instead it's meant as more of a "cheat-sheet" of important language features that might come in handy. You may find it useful to skim through the table-of-contents on the left to search for any features you don't recognise or need a quick reminder on.

C# 5.0 

Async and Await 

This feature is a mainstay of C# and has had such an impact on the industry that it's found its way in to other mainstream languages. There are countless tutorials and books that go deep in to this feature; but this post is just meant as a quick reference guide, so I will only summarize it here.

Async/await allows methods to use asynchrony but be written in a synchronous manner:

// async keyword tells the compiler we're writing an async method
// Task<int> is a 'Future'/Promise that will eventually yield a value of type int
async Task<int> GetUserAgeFromDatabase(string username) {
	// await keyword tells the compiler to convert this method in to a state machine at this point
	// Method will return the Task<int> immediately at this point (assuming GetUserDetails() does not complete immediately and synchronously)
	// A continuation for the remainder of the method will be scheduled either on this thread (via captured context) or on task pool thread (by default) to be executed once GetUserDetails()'s Task has completed
	var userDetails = await _databaseAccessLayer.GetUserDetails(username);
	
	// Once we're here, we're executing the continuation
	return userDetails.Age;
}
View Code Fullscreen • "Async/Await"

Caller Info Attributes 

This feature involves three attributes that can be applied to optional method parameters. The compiler will then fill in the details; these are useful mostly for logging:

static void Log(string message, [CallerMemberName] string callerMemberName = null, [CallerFilePath] string callerFilePath = null, [CallerLineNumber] int callerLineNumber) {
	Console.WriteLine($"{message} (called from {callerMemberName} on line {callerLineNumber} in file {callerFilePath})");
}

static void Test() {
	Log("My message"); // Will print something like "My message (called from Test() on line 15 in file C:\...\Example.cs)"
}
View Code Fullscreen • "Caller Info Attributes"

C# 6.0 

Static Imports 

This feature allows usage of static methods on a class without using the class name:

using static System.Console;

static void Test() {
	WriteLine("hello"); // No Console. prefix required
}
View Code Fullscreen • "Static Imports"

Exception Filters 

Exception filters allow catching exceptions only when certain parameters are met:

static void Test() {
	try {
		SomeOperation();
	}
	catch (Exception e) when (e.InnerException is OperationCanceledException oce) {
		Console.WriteLine($"Operation was cancelled: {oce}");
	}
}
View Code Fullscreen • "Exception Filters"

Immutable Auto-Properties 

This feature allows omitting the setter from an auto-property to make it immutable:

class MyClass {
	public string Name { get; }
	
	public MyClass(string name) {
		Name = name; // Can be initialized in constructor
	}
}
View Code Fullscreen • "Immutable Auto-Properties"
Name can not be set from any place other than the constructor (or inline, see next feature).

Auto-Property Initializers 

This allows setting of an initial value for a property inline at its declaration point:

class MyClass {
	public string Name { get; } = "Ben";
	public int Age { get; set; } = 30;
}
View Code Fullscreen • "Auto-Property Initializers"

Expression-Bodied Members 

This feature allows writing certain function bodies as single-line expressions:

class MyClass {
	// Age is a read-only property; the code to the right of the '=>' is evaluated every time the property is invoked and the result of the expression is returned
	public int Age => (int) (DateTime.Now - new DateTime(1990, 01, 19)).TotalYears;
	
	// PrintAge is a method, the code to the right of the '=>' is executed when the function is invoked
	public void PrintAge() => Console.WriteLine(Age);
}
View Code Fullscreen • "Expression-Bodied Members"
Some further support was added in C# 7.0:

class MyClass {
	int _age;
	
	// Property getter and setter
	public int Age { 
		get => _age;
		set => _age = value >= 0 ?? value : throw new ArgumentOutOfRangeException(nameof(value));
	
	// Constructor
	public MyClass(int age) => Age = age;
	
	// Finalizer
	~MyClass() => ResourceManager.NotifyFinalizedMyClass(this);
}
View Code Fullscreen • "More Expression-Bodied Members"

Null Propagation ("Elvis" Operator) 

This operator allows you to access members of an object if that object is not null; or simply fold all values to null otherwise:

static void PrintUserName(UserDetails? user) {
	Console.WriteLine($"Name: {user?.Name ?? "No name"}, Age: {user?.Age.ToString() ?? "No age"}");
}

static void Test() {
	PrintUserName(new UserDetails("Ben", 30)); // Prints "Name: Ben, Age: 30"
	PrintUserName(null); // Prints "Name: No name, Age: No age"
}
View Code Fullscreen • "Null-Conditional Operator"
When chaining multiple property/method/field invocations together (i.e. var x = a?.B?.C()?.D) the entire expression will return null if any individual element is null in the chain.

String Interpolation (and FormattedStrings) 

This is a feature I've been using already in various examples so far. String interpolation allows for a more natural way of embedding variables in to strings:

static void Test() {
	var name = "Ben";
	Console.WriteLine($"My name is {name}"); // The $ sign before the opening quotemark indicates this is an interpolated string
}
View Code Fullscreen • "Basic String Interpolation"
The way values are converted to string can be specified via format suffix. The following example shows one way to specify a number of decimal places when converting a floating-point value to a string:

static void Test() {
	var percentageComplete = 12.345d;
	Console.WriteLine($"Percentage complete: {percentageComplete:F0}%"); // Prints "Percentage complete: 12%"
	Console.WriteLine($"Percentage complete: {percentageComplete:F2}%"); // Prints "Percentage complete: 12.34%"
}
View Code Fullscreen • "Formatted String Interpolation"
It's also possible to specify an alignment; useful for printing ASCII tables:

static void Test() {
	var names = new[] { "Ben", "Javier", "Chris" };
	var favoriteFoods = new[] { "Ramen", "Something Vegetarian", "No idea" };
	
	for (var i = 0; i < 3; ++i) {
		Console.WriteLine($"Name: {names[i],10} | Food: {favoriteFoods[i]}"); // Notice the ,10 that right-aligns names to a 10-column width
	}
}

/* Prints:
 * Name:        Ben | Food: Ramen
 * Name:     Javier | Food: Something Vegetarian
 * Name:      Chris | Food: No idea
*/





static void Test() {
	var names = new[] { "Ben", "Javier", "Chris" };
	var favoriteFoods = new[] { "Ramen", "Something Vegetarian", "No idea" };
	
	for (var i = 0; i < 3; ++i) {
		Console.WriteLine($"Name: {names[i],-10} | Food: {favoriteFoods[i]}"); // Notice the ,-10 that left-aligns names to a 10-column width
	}
}

/* Prints:
 * Name: Ben        | Food: Ramen
 * Name: Javier     | Food: Something Vegetarian
 * Name: Chris      | Food: No idea
*/
View Code Fullscreen • "Aligned String Interpolation"
The default formatting for objects uses the thread-local culture as a format provider. Sometimes this isn't what we want. Therefore, we can manually specify a format provider by explicitly creating a FormattableString and then converting it to a string after:

static void Test() {
	var percentageComplete = 12.345d;
	FormattableString str = $"Percentage complete: {percentageComplete:F2}%";

	Console.WriteLine(str.ToString(CultureInfo.GetCultureInfo("de-DE"))); // Prints "Percentage complete: 12,35%" (German-style number formatting)
}
View Code Fullscreen • "FormattableString"

The "nameof" Operator 

This small feature allows you to convert the name of a token in code to a string. It's useful because it avoids issues with manual writing out of member/type names when those types are renamed:

class User {
	public string Name { get; }
	
	public User(string name) {
		if (name == null) throw new ArgumentNullException(nameof(name)); // If we rename name later this will not compile (which is good)
		Name = name;
	}
}
View Code Fullscreen • "Nameof Operator"

Alternative Initialization Syntax for Associative Collections 

This is a small feature. It allows a cleaner syntax for initializing associative collections (i.e. mostly dictionaries). The following two initializations are identical:

class User {
	static void Test() {
		var oldWay = new Dictionary<int, string> {
			{ 1, "One" },
			{ 2, "Two" },
			{ 3, "Three" },
			{ 4, "Four" }
		};

		var newWay = new Dictionary<int, string> {
			[1] = "One",
			[2] = "Two",
			[3] = "Three",
			[4] = "Four"
		};
	}
}
View Code Fullscreen • "Old vs New Dictionary Initialization"
The dictionary keys and values can be of any type.

Extension 'Add' Methods for Collection Initializers 

Let's say a collection type (must implement IEnumerable<>) is defined in a library you're using; but the method for adding elements isn't named Add(T item). This is a requirement for collection initializers to work.

Here's an example using an imaginary type named UserDatabase that implements IEnumerable<User> from an imaginary third-party library:

static void Test() {
	// Won't compile
	// Doesn't work becuase UserDatabase calls its add method AddUser(), so we have to use the second approach below
	var database = new UserDatabase {
		new User("Ben", 30),
		new User("Seb", 27),
		new User("Rob", 33)
	};
	
	// Will compile but less pretty
	var database = new UserDatabase();
	database.AddUser(new User("Ben", 30));
	database.AddUser(new User("Seb", 27));
	database.AddUser(new User("Rob", 33));
}
View Code Fullscreen • "No Add Method when Trying to Use Collection Initializer"
In this scenario, starting with C# 6.0, we can specify an Add(T item) extension method to enable collection initializers:

static class UserDatabaseExtensions {
	public static void Add(this UserDatabase @this, User u) => @this.AddUser(u);
}

// ...

static void Test() {
	// Hooray, this works now!
	var database = new UserDatabase {
		new User("Ben", 30),
		new User("Seb", 27),
		new User("Rob", 33)
	};
}
View Code Fullscreen • "Add Extension Method when Trying to Use Collection Initializer"