Source code
Best Practices C#

Significant Features of C# Versions 3.0 to 8.0 You Will Find Useful

Welcome to today’s blog.

Today I will be covering some features of C# language that have been introduced into the compiler within the past decade.

Unless you were in an IT department that never upgraded its compiler tools for a while, or were you one of those developers that never bothered to learn the new compiler features in the shiny new Visual Studio version because you just wanted to develop web applications, or were you one of those pre-2000 IT guys who learned C or C++ programming at university and thought you knew C# because you had K&R’s The C Programming Language (1984 edition), the ultimate reference gathering dust on your bookshelf for the past two decades?

It’s time to wake up!

In the past eight years there have been several releases of the C# language that have provided useful improvements to the language.

How often have you been asked in a developer interview about recent or new features that have been introduced into the C# language, and did not have a clue because you were still stuck in time warp with C# 1.0 syntax in your head?

A number of these features are becoming a part of the best practices that are being employed in C# code within the past few years, and if you are a developer that is still using some of the older features of the language, then it is time to familiarize yourself with these features as they can improve your code by making it cleaner and more readable.

C# 3.0 (c2007)

Version C# 3.0 introduced as part of the VS 2008 release, among the most significant releases were query (LINQ) expressions, lambda expressions, implicit typing, and extension methods. I will go through these features.

Query expressions

Query expressions or LINQ queries allow any data source including internal data structures, collections, and data base entities to be queried using the LINQ query syntax. This allows a query to be constructed that returns data in the form of a collection of IEnumerable or IQueryable types.  

Below are examples of querying with an aggregation and a list:

int booksOfGenreCount =
    (from book in books
     where genre = "comedy"
     select book)
     .Count();
IList<Book> booksOfGenre =
    (from book in books
     where genre = "comedy"
     select book).ToList();

Lambda expressions

Lambda expressions allow you to create anonymous functions. The use of the lambda operator => segregates the parameter list from the function body.

Examples are shown below:  

A function with tuple parameters:

Func<int, int> cube = x => x * x * x;

A lambda expression with query operator:

int[] allNumbers = { 1, -2, 3, -4, 5, -6, 7, -8, 9, -10 };
int sumAllPostiveNumbers = allNumbers.Count(n => n > 0);

Implicit typing

With implicit types, the type of variables is strongly typed, with the type determined at runtime. Unlike explicitly typed variables, declarations with a var need to have a value assigned to the variable. An example is shown below:

var x = 1; 

They can be used in looping constructs as shown:

foreach (var item in list) 
{ 
  .. 
}

Implicitly typed variables can also be used in LINQ queries:

var varBooks =
  from b in books
  select new { Title = w.title, Author = w.author };

Extension methods

Extension methods are static methods that allow classes to be extended without having to override them. An example of an extension of the string class is shown below:

public static class StringExtension
{
    public static int SentenceSpaceCount(this String str)
    {
        return str.Count(Char.IsWhiteSpace);
    }
}

To use an extension class method is like using any instance class:

string s = ""How many spaces are in this string?"
int i = s.SentenceSpaceCount();

C# 4.0 (c2010)

Version C# 4.0 introduced as part of the VS 2010 release, had one significant addition and that is the dynamic keyword.

Dynamic keyword

What the dynamic keyword did was to override static compiler-time type checking and allow for run-time checking. What this did was to allow a type on a variable to be determined during runtime.

The following statement allows us to set a value on a dynamically typed variable:

dynamic a = “this is a string”;

When the statement is run, the type of the variable a will be of string type.

This is quite powerful when the type of the value that is going to be assigned is unknown at the time the application is run.

Named and optional arguments

Another feature introduce is that of named and optional arguments.

Named arguments

What the named arguments feature allows you to do is specify parameter arguments clearly without having to ambiguously guess the ordering of parameters.

An example is this method:

void AddNewBook(string title, string author, DateTime datePublished)
{
    ...
}

To call this method we make the following call:

AddNewBook(“The Hobbit”, “J. R. R. Tolkien”, “1937-09-21”);

Without knowing the functional prototype of the method, we do not know if we are assigning the title to the author or vice versa! The first two parameters are strings, so a developer could accidently pass these in the wrong order.

What named parameters allows is to name each parameter so that we know where each value is going to be matched to in the methods’ parameter list.

The following is how named parameters are used in the above call:

AddNewBook(title: “The Hobbit”, author: “J. R. R. Tolkien”, datePublished: “1937-09-21”);

And named parameters will still work as expected irrespective of positioning:

AddNewBook(datePublished: “1937-09-21”, author: “J. R. R. Tolkien”, title: “The Hobbit”);

Optional arguments

The feature of optional arguments allows default parameters to be specified in a function call. In a method’s function prototype, we can specify if an argument is default.

An example is shown below:

List<Books> FilterListOfBooks(string titleMatch, bool showDates = false, 
bool showDescription = false);

The above shows that the two boolean arguments are defaulted, so they are optional in any call. The following are valid calls to the function:

FilterListOfBooks(“Hobb”);
FilterListOfBooks(“Hobb”, true);
FilterListOfBooks(“Hobb”, true, true);
FilterListOfBooks(“Hobb”, showDescription: true);

C# 5.0 (c2012)

Version C# 5.0 introduced as part of the VS 2012 release, had one significant feature which is used prominently today within web application and API development using .NET Core. That feature is the async feature.

This replaced objects like the BackgroundWorkerProces and the Task Parallel Library

An example is shown below:

[HttpGet("api/[controller]/List")]
public async Task<List<BookViewModel>> List()
{
    try
    {
        List<BookViewModel> bvm = new List<BookViewModel>();
        return await _db.Books.ToListAsync();
    }
    catch (Exception ex)
    {
        throw;
    }
} 

C# 6.0 (c2015)

Version C# 6.0 introduced as part of the VS 2015 release, introduced a number if useful features. These are explained below.

String interpolation

String interpolation allows you to embed expressions in a string.

Examples are shown:

public string FullName => $"{FirstName} {LastName}";
int Age = 21;
public string FullNameAndAge () =>
   $"Name: {LastName}, {FirstName}. Age: {Age}";

These would replace a cumbersome initialization:

public string FullNameAndAge;
FullNameAndAge = String.Format(“Name: {0}, {1}. Age”, FirstName, LastName, Age); 

Auto-property initializers

An auto-property initializer initializes a property in one line of code instead of two. The line:

public ICollection<double> Grades { get; } = new List<double>();

replaces the following:

public ICollection<double> Grades { get; } 
..	
ICollection<double> Grades = new List<double>();

Null-conditional operators

One of the most frequently used features. Similar to a NULLIF() from SQL, the null conditional operator short-circuits the evaluation when the first part of the expression is null.

In this expression:

var firstName = person?.firstName;

When person variable value is null, the value firstName will be null, else it will be assigned to person.firstName, avoiding a NullReferenceException.

This replaced the ubiquitous if .. then .. else statement:

If (!person)
    firstName = null;
if (person.firstName)
    firstName = person.firstName;

Null-coalescing operator

The null coalescing operator is used with the null-conditional operator. The difference being that the final expression will have a default value instead of null.

For example:

var firstName = person?.firstName ?? "Not specified";

When the expression short-circuits, the value returned is the right-hand expression.

C# 7.0 (c2017)

Out variables

Out variables have been part of the C language for decades. The new feature introduced in this release is allowing the declaration of the out parameter within the argument list for the method call. This saves an additional line of code spent declaring the parameter locally.

An example is shown below:

if (int.TryParse(input, out int result))
    Console.WriteLine(result);
else
    Console.WriteLine("Cannot parse input");

Which replaces the commands:

int result;
if (int.TryParse(input, out result))
    Console.WriteLine(result);
else
    Console.WriteLine("Cannot parse input");

Tuples and discards

Tuples

Tuples are simple data structures that contain multiple fields which represent data members.

An example is shown:

(string X, string Y) coordinates = ("x", "y");
Console.WriteLine($"{coordinates.X}, {coordinates.Y }");

When members of a tuple are returned from a method, separate variables can be declared for each tuple value. This is known as deconstructing a tuple.

For example:

(string X, string Y) coordinates = getCoordinates();

Discards

When deconstructing a tuple or when we call methods with out parameters, in some cases we have had to declare output variables that we would not be using in the calling block. The feature of discards avoids this redundancy by providing a stub or placeholder in the form of a _ character for the output value we don’t use.

An example is shown below where the method returns a tuple and we only require the mayor name in a council:

(_, _, mayor) = address.GetCouncilInformation(suburbName);

When using the out parameter for a method that sets output parameters within the method, only the used output parameter(s) are declared:

address.GetCouncilInformation(out _ out _, out var mayor);

C# 8.0 (c2020)

Note: C# 8.0 has support from .NET Core 3.x and .NET Standard 2.1. 

Using declaration

In previous versions, the using declaration is used to open a resource within a block of code, then free the resource when the block exits:

static int WriteLinesToFile(IEnumerable<string> lines)
{
    using (var file = new System.IO.StreamWriter("WriteLines2.txt"))
    {
        int skippedLines = 0;
        foreach (string line in lines)
        {
	        …
        }
        return skippedLines;
    } // file is disposed here
}

In the new version, the using declaration does not require subsequent code to be enclosed within a block and automatically frees the resource at the end of the method block the resource is declared.

This is done as shown:

static int WriteLinesToFile(IEnumerable<string> lines)
{
    using var file = new System.IO.StreamWriter("WriteLines2.txt");
    int skippedLines = 0;
    foreach (string line in lines)
    {
	…
    }
    return skippedLines;
    // file is disposed here
}

These are the most used features I have encountered, but there are several other useful features you can experiment with you make your code cleaner. For more detailed reference, refer to the Microsoft web site for the C# version history.

That is all for this post.

I hope you found this post useful and informative.

Social media & sharing icons powered by UltimatelySocial