Stackify is now BMC. Read theBlog

3 New C# 8 Features We Are Excited About

By: Simon
  |  March 18, 2023
3 New C# 8 Features We Are Excited About

C# is rapidly approaching its third decade of life. Originally released in 2000, the language has grown and changed across 7 major versions. Once a knock off of Java in all but name has jumped out ahead on many aspects.

Throughout its life, the release of a new version of the language has been highly coupled with releases of new versions of Visual Studio as well as with releases of the .NET runtime. This coupling has actually reduced the pace of innovation.

If a feature was not quite ready for release by the time a cutoff was reached then it would be forced to wait for the next release which was, most likely, two years away.

Increasing Pace of C# Releases

Under, what many have called, the new Microsoft, the pace of innovation has increased. To achieve this, the developer division at Microsoft has decoupled a number of technologies which used to be shipped all together.

Visual Studio, the .NET Framework, the .NET runtime, the compilers and the languages that compile to run on the runtime have all been split into their own packages and versions. This means that each one can be released at its own cadence. We can see the success of this model already with the work done on .NET Core 2 which, released out of band from Visual Studio, has added support for Linux. At almost the same time as .NET Core 2, C# 7.1 was released.

C# 7.1 is an important release, not so much for the new features, but for the fact that it is the first point release of C#. There is already work ongoing towards C# 7.2 and it looks like there are also plans in the works for a C# 7.3. All this means that C# 8 is quite a way off in the future. None the less we have a few ideas about what could be in C# 8. All of the concepts in the article are proposals for inclusion in C#8 but some of them, or all of them, may not make it.

The syntax is also the best guess and is subject to change. The best source of information on the evolution of the C# language is on the GitHub repository for the C# language at https://github.com/dotnet/csharplang.

Language design discussions are public and there is an extensive discussion in the issues section around new features. Check the milestones for which issues are tentatively scheduled for each release. They provide a nice estimate of the future C# roadmap.

Proposed C# 8 Features

Non-Nullable & Nullable Reference Types

C# has two variable types: primitives and reference types. Primitives are the likes of int, char and double. These types cannot take on the value of null. Creating a new int without assigning a value will result in an int with a value of 0 rather than a null. C# 2.0 introduced nullable versions of the various primitives which are denoted by a “?”. Thus int? is a version of int which can take on a null value.

On the flip side, reference types (any object such as string) have always been able to take on a null value and have null as the default. This has the unfortunate disadvantage of allowing null references to sneak into applications.

New in C# 8 will be an opt-in feature for making reference types non-nullable. 

Retrofitting a concept like this into C# is quite difficult as it has the potential to cause compilation errors in code which have previously been fine. Thus what is needed is a way to create this functionality without creating an insurmountable amount of work for developers.

Per the design proposal, the C# team has decided to take an approach of allowing developers to opt into nullable reference types. There will be a project level setting to enable validation of nullable references. Once enabled, objects that can take on a value of null will need to be declared using the same ? operator that is used for nullable int and similar.

Code such as

String s = null;
Console.Write(s);

Will cause a warning because String cannot take on a value of null. Instead the code

String? s = null;
Console.Write(s);

Would need to be used. However, this code would also throw a warning because of Console.Write is not expecting to receive a nullable string. In fact, the original code was likely in error so the cascade of warning is helping us avoid a runtime error.

To me, this is the language change which has the most potential to improve the quality of code which is produced. F# has long been a favorite of mine for writing error free code because of just this avoidance of runtime null exceptions. I suspect that the number of warnings one might find upon activating the opt-in on a larger code base will be disheartening but the errors should be quite easy to address and the resulting code more resilient.

[adinserter block=”33″]

New Lightweight Classes: Records

A nice new C# 8 feature is a new way to create a C# class called records. They are essentially a very lightweight class that is a collection of fields. They help quickly create POCO type objects and also solve a key problem around comparing equality between objects.

For instance, creating a record type for a bank account might look like

class BankAccount(Guid Id, string Name, decimal Balance)

This would be expanded out to a much larger class that implements IEquatable. It is a nice shorthand way to create simple classes.

Solving Object Equality with Records

Probably one of the most difficult to grasp concepts for those new to programming in C# is the difference between how the == operator works with reference types vs. primitives. Comparing two integers using == gives exactly what one would expect

int I = 1;
int j = 1;
i == j //yields true

The values of primitives are compared. However, for reference types, the same is not true

Object I = new Object();
Object j = new Object();
i == j //yields false

This is because C# compares reference types for referential equality, that is to say, that if the object is the same object it is equal. Record types provide structural equality, in effect implementing the equality operator. The syntax for creating a new record is very terse because the resulting objects are simple data transport objects.

Record types are another idea which exists in other languages such as F#. Lightweight objects such as these are highly convenient. While not a groundbreaking change to the language this is an incremental improvement which is welcome.

Default Interface Implementations

Versioning interfaces can be annoying because it requires that new method on the interface be implemented on all the objects which implement the interface. As new methods are added to interfaces the burden of implementing them falls to the various classes implementing the interface. Because the implementers do not necessarily have a common ancestor, methods added to the interface must be implemented independently on each class.

Default interface implementations allow specifying an implementation in the interface so long as it is implemented as a function of existing methods on the interface. If we continue with a bank account example we might have an existing interface that looks like

public interface IBankAccountManager{
    void PerformTransaction(decimal amount, string reason);
}

Now for usability purposes, we’d like to make explicit debit and credit functions on the bank account. Normally we’d add these to the interface and then go implement them on all the classes that implement IBankAccountManager (ISavingsAccountManager, IChequingAccountManager,…). With a default implementation, we could write default implementations in the interface itself.

public interface IBankAccountManager{ 
  void PerformTransaction(decimal amount, string reason); 
  void PerformDebit(decimal amount, string reason){ 
    PerformTransaction(-1 * amount, $”Debit: {reason}”); 
  } 
 
  void PerformCredit(decimal amount, string reason){ 
    PerformTransaction(amount, $”Credit: {reason}”); 
  } 
}

Default implementations of interfaces provide a powerful new way to extend classes that implement interfaces without having to duplicate code. The result is not quite a mix in but certainly somewhere along the path. Classes that implement many interfaces could be greatly simplified by just deferring to the default implementation.

Other New C# 8 Features

Several other changes will no doubt make it into C# 8. So far we have focused on the ones I am the most excited about. Here are a couple other notable proposals that have been discussed:

  • Improved Extension Support – Ability to use more than just extension methods. Adding support for properties, static methods and much more.
  • Async Streams  – Ability to have enumerators that support async operations. Including new IAsyncEnumerable<T> and IAsyncEnumerator<T> interfaces.
  • Async Disposable – IAsyncDisposable would allow objects to have an async Dispose method.

Conclusion

There are a number of other great improvements to C# waiting in the wings that we’ll cover in the next blog post.

The pace of innovation in the .NET space has really picked up in the last few years. The Roslyn compiler has made experimenting with new language features easier than ever and being able to ship very small releases has reduced the time to market. Although it has yet to be implemented, C# 8 has the potential to bring a number of helpful improvements over C# 7. I know that I’m excited to replace all my data objects with syntactically terse record types and see in just how many places I have potential null references.

Remember to always have a good APM in place too for your .NET applications. Stackify Retrace is a full lifecycle APM and you can trial it here.

References:

Improve Your Code with Retrace APM

Stackify's APM tools are used by thousands of .NET, Java, PHP, Node.js, Python, & Ruby developers all over the world.
Explore Retrace's product features to learn more.

Learn More

Want to contribute to the Stackify blog?

If you would like to be a guest contributor to the Stackify blog please reach out to stackify@stackify.com