CSharp 8.0 new features

In this two part article we will discuss some of C# 8.0 new features and enhancements. We will try to explain each feature in details and provide examples for it. 

Contents

Why C#

As you know c# is one of the most modern programming languages. It’s simple and easy to use. We can use it to develop windows, web and even mobile applications. Almost each year Microsoft announce a new version of it with lots of useful new features. 

I’ve started my programming journey with c and c++ with one of my friend. We were trying to write a football match predictor application that days. No doubt that the application didn’t worked 🙂 but it helped us to start writing some code. After a while we decided to create a web application so we research about what language we should learn . As we were already familiar with c and c++, choosing asp.net with c# was the most logical decision for us. Since then, we continue using C# for most of our applications. 

How to use C# 8.0

Finally C# 8.0 is out. Microsoft released Visual Studio 2019 16.3 along with dot-net core 3.0 and C# 8.0. There are lots of cool and handy features in this release. We are going to talk about some of useful and general features of C# 8.0 in this article.

C# 8.0 is default language version of .Net core 3.0 and .Net standard 2.1 projects. So if you are using them you are good to go. Otherwise you should simply override old lang version in .csproj file :

<LangVersion>8.0</LangVersion>

The C# 8.0 compiler is available starting with Visual Studio 2019 version 16.3 or the .NET Core 3.0 SDK.

Whats new in C# 8.0

C# 8.0 comes out with lots of features and enhancements. We are not going to talk about all of this features. But we will discuss most of useful and exciting features that any C# developer can use.

Nullable Reference Types

All of us have been faced NullReferenceException in our c# programming life. we declare a variable as reference type assign null, then we try to use one of it’s properties. This is one of most common run-time exceptions. Microsoft team decided to do something about it. From now reference types will be checked for not null assignment before use. C# compiler will warn you about assigning null values to not null types.

But the first Question that came to my mind when I read this title was “Isn’t reference type already nullable ?”. This title is somehow confusing. In fact Microsoft team changed the default behavior of declaring reference types. so reference type variables aren’t by default nullable. In order to make them nullable, you must append the type name with the ? to declare the variable as a nullable reference type.

Let’s look at a sample code:

#nullable enable 
// let's first assign null to not nullable type 
string name = null;  // warning: Converting null literal or possible null value to non-nullable type 
// to fix this we should use ? to declare a nullable type 
string? name = null; // This is okey 
#nullable restore

As you notices we used #nullable enable and #nullable restore directives to enable and restore this feature. This is because of existing legacy code. Nullable reference type isn’t by default enabled. This directives let you enable and disable it for just few lines of code. The nullable annotation context and nullable warning context can be also set for entire project using the Nullable element in your .csproj file. 

<Project Sdk="Microsoft.NET.Sdk"> 
 <PropertyGroup> 
  <OutputType>Exe</OutputType> 
  <TargetFramework>netcoreapp3.0</TargetFramework> 
  <Nullable>enable</Nullable> 
 </PropertyGroup> 
</Project>

Now that we learned how to enable nullable reference type, let’s see some more code:

 public class User 
 { 
  public string Name { get; set; } 
 } 

 public static void SayHello(User? user) 
 { 
  Console.WriteLine("Hello " + user.Name); // Warning: Dereference of a possibly null reference 
 }

In the above example we tried to use a nullable type in SayHello method without null checking. Compiler warn us about possible null reference.

To fix it:

public static void SayHello(User? user) 
{ 
 if(user != null) 
  Console.WriteLine("Hello " + user.Name); 

 Console.WriteLine("User not defined"); 
}

Indices and ranges

Do you like syntax of selecting last element of an array colors[colors.Length - 1]. Did you always think there should be a better way to select a range values from an array. Thanks to C# 8.0 there are a great improvement here. C# now supports two new types and two new operators.

With System.Index and ^ operator you may define an index variable like below:

Index i1 = 0; // index of first element
Index i2 = ^1; // index of last element

Now suppose that we an array of colors:

var months = new string[]
{
 "January",
 "February",
 "March",
 "April",
 "May",
 "June",
 "July",
 "August", 
 "September", 
 "October",
 "November", 
 "December"
};

string firstMonth = months[i1]; // January
string lastMonth = months[i2]; // December
string lastMonth = months[^1]; // December
string secondFromLast = months[^2]; // November

The months[^0] index is the same as months[months.Length]. This will cause a System.IndexOutOfRangeException exception. We will use ^0 index later to selecting a sub range of a sequence.

  • System.Range
  • The range operator ..

With new System.Range type and .. operator we could declare a sub range of sequence. for example:

// Declare Range as variable
System.Range r1 = 0..3;
System.Range r2 = 0..^0; // entire sequence

// User declared variable between '[' and ']'
var fist3Months = months[r1]; // { "January", "February", "March" }
// or use it inline
var fist3Months = months[0..3]; // { "January", "February", "March" }
var last3Months = months[^3..^0]; // { "October", "November", "December" }

A range specifies the start and end of a range. The start is included in the range but the end is not included in the range. You may also specify only start or only end or none of them. Let’s see it in following example:

var fist3Months = months[..3]; // { "January", "February", "March" } 
var last3Months = months[^3..]; // { "October", "November", "December" }
var allMonths = months[..]; // all months

Default Implementations of Interface Members

C# 8.0 enables you to provide implementation for interface members. Why this is required? This new feature will let you to extend existing interfaces without breaking anything. Assume you already deployed your application. Now you decide to add new member to one of interfaces. By using default implementation, you can provide an implementation for the new member. So any existing class inheriting this interface will use this implementation and they don’t need to implement it separately.

Another reason of bringing this feature to C# is because of Android and Swift which have similar feature in their languages. So Default Implementation of Interface will be useful in cross-platform mobile development with C# language.

Let’s see this in an example:

Assume we designed an application for online store with multiple products. In our application, all product classes inherit from IProduct interface.

public interface IProduct
{
 decimal Price { get; set; }
 string Name { get; set; }
 DateTime DateAdded { get; set; }
}

For example SampleProduct inherits from IProduct interface :

class SampleProduct : IProduct
{
 public SampleProduct(string name, DateTime dateAdded, decimal price)
{
Name = name;
DateAdded = dateAdded;
Price = price;
} public decimal Price { get; set; } public string Name { get; set; } public DateTime DateAdded { get; set; } }

After a while we decide to apply 20 percent discount for all products that exists in store for more than three months. We can achieve this new requirement by using Default Implementation for Interface members like below:

public interface IProduct
{
 decimal Price { get; set; }
 string Name { get; set; }
 DateTime DateAdded { get; set; }

 decimal CalculateDiscount()
 {
   DateTime ThreeMonthsAge = DateTime.Now.AddMonths(-3);
    if (DateAdded < ThreeMonthsAge)
    {
     return Price * .2m;
    }
    return 0;
  } 
}

Now we can use this new method like belw : 

SampleProduct sampleProduct = new SampleProduct("Product One", new DateTime(2019, 7, 1), 120);
IProduct p = sampleProduct; // cast required

Console.WriteLine($"this Product has ${p.CalculateDiscount()} $ discount!"); //this Product has $24.0 $ discount!

It is required to cast SampleProduct to IProduct. The SampleProduct class doesn’t need to provide an implementation for CalculateDiscount. In order to call any method declared and implemented in the interface, the variable must be the type of the IProductinterface.