✅ C# 9.0 Features
The latest version of C#, 9.0, was officially released with .NET 5 in November 2020. These days there are already rumors of the features of the future version, C# 10. ✅
One of the biggest advantages of open source software is being able to see how the project evolves over time as the days go by. With this, we want to refer to the same C#, since we can follow its progress on GitHub and see its main news.
Let's start with the most important features of C# 9.0 🤗
🔼 Module Initializers
In this latest version of C # 9.0, the [ModuleInitializer] attribute is used to specify a method that we can invoke before any code in the module, the destination method must be static, without any type of parameter and returned empty.
using system;
using System.Runtime.CompilerServices;
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"Data={Data}");
}
public static string Data;
[ModuleInitializer]
public static void Init()
{
Data="This static method is invoked before any other method in the module";
}
}
🔼 Extension GetEnumerator
The foreach statement normally operates on a variable of type IEnumerator when it contains a definition of any public extension for GetEnumerator.
This is how we can see it in this example 👇
using system;
using System.Collections.Generic;
IEnumerator < string > colors = new List < string > {
"blue",
"red",
"green"
}.GetEnumerator();
foreach(var colors in colors) {
Console.WriteLine($ "{color} is my favorite color");
}
public static class Extensions {
public static IEnumerator < T > GetEnumerator < T > (this IEnumerator < T > enumerator) => enumerator;
}
🔼 Covariant Return Types
In C# 9.0, the return types of override methods are usually much more specific than the declarations in the base type 👇
abstract class Weather {
public abstract Temperature GetTemperature();
}
class Spain: Weather {
public override Celsius GetTemperature() => new Celsius();
}
class USA: Weather {
public override Farenheit GetTemperature() => ne Farenheit();
}
class Temperature {}
class Celsius {}
class Farenheit {}
The GetTemperature () method has the return type Temperature, the derived class Spain overrides this method and returns a specific type Celsius.
It is a feature that makes our code more flexible. ✅
🔼 Init Accessor
The init accessor makes immutable objects easier to create and use 👇,
Point point1 = new() { X = 1, Y = 2};
Console.WriteLine(point1.ToString());
public record Point
{
public int X { get; init;}
public int Y { get; init;}
}
The init accessor can be used with Structures, Registers, and Classes. The init accessor can be used with Classes, Structures, and Registers 👇,
Point point1 = new() { X = 1, Y = 2};
Point point2 = point1 with { Y = 4};
Console.WriteLine(point1.ToString());
public record Point
{
public int X { get; init;}
public int Y { get; init;}
}
🔼 Records
Now we have a new type of reference called the record that gives us equal value. To better understand it, we have this example 👇,
Point point1 = new(1, 2);
Console.WriteLine(point1.ToString());
Point point2 = new(1, 2)};
Console.WriteLine(point1.Equals(point2));
public record Point
{
public int X { get;}
public int Y { get;}
public Point(int x, int y) => (X, Y) = (x, y);
}
As we can see, the point record is immutable, you can greatly simplify the syntax using an init accessor since its properties are read-only.
🔼 Lambda Discard Parameters
The next C# 9.0 improvement is being able to use discard (_) as an input parameter of a lambda expression in case that parameter is not used.
//C#8
button.Click += (s, e) => {Message.Box.Show("Button clicked"); };
//C#9
button.Click += (_, _) => {Message.Box.Show("Button clicked"); };
It is a feature that also allows us to read the code in a cleaner and more beautiful way.
🔼 Target-Typed new
Another very important feature in this latest version of C# is the ability to omit the type of a new expression when the object type is explicitly known.
Let's see a quick and simple example 👇
Point point = new() {X = 1, Y = 2};
Console.WriteLine($"point:({point1.X}, {point.Y})");
public class Point
{
public int X { get; set; }
public int Y { get; set; }
}
It is a very useful feature since it allows you to read the code in a clean way without having to duplicate the type.
Point point = new(1, 2);
Console.WriteLine($"point:({point1.X}, {point.Y})");
public class Point{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
}
🔼 Top-Level Statements
In C# 9.0, it is possible to write a top-level program after using declarations.
Here we can see the example 👇
using System;
Console.WriteLine("Hello World!");
With top-level declarations, you wouldn't need to declare any space between names, main method, or class program. This new feature can be very useful for programmers just starting out, as the compiler does all of these things for you.
[CompilerGenerated]
internal static class $program
{
private static void $main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
Seeing the new features in C# 9.0, which help make programming much simpler and more intuitive.
✅ C# 10 Expectations
What can we expect in the future version?
Okay, let's talk about the future version 👇
- What could bring new? 🤔
- What would you like me to have? 🤔
- What is the possibility of it being added?🤔
Note that the upcoming features are still debatable and are not certain to appear in C# 10.
Keep in mind that they are not simply ideas or contributions from the community. These features that I am going to mention are being shuffled by its developers. And although they are not implemented in the next version, today they are being refined so that they come out in future versions of C#.
Let's start 👍,
🔼 File-level namespaces
When we started programming in C# we have created a "Hello World" application. Knowing this we also know that C# uses a block structure for namespaces.
namespace HelloWorld
{
class Hello
{
static void Main(string[] args)
{
System.Console.WriteLine("Hello World!");
}
}
}
The best thing about this is that namespaces can be overlaid very easily, simply by nesting blocks. In the same way that a single file can contain types in any combination of namespaces and multiple files can share the same namespace between them.
If we want to scratch the negative part a bit, this system adds a bit of indentation if we compare it with bracket languages such as JavaScript or Java.
The question we ask ourselves at this point is:
Is it possible to keep that functionality, but at the same time reduce excess indentation? 🤔
Yes ✅
How is it possible? 🤔
It simply opened that entering namespaces with file scope, this would allow establishing a default namespace that would be applied automatically to the entire file eliminating the indentation.
namespace HelloWorld; public class Hello
{
static void Main (string[] args)
{
System.Console.WriteLine("Hello World!");
}
}
It is normal to only have one file scoped namespace per file, so there would be no problem. Likewise, most C# code files do not include more than one namespace.
If for example, we add a namespace block to a file that uses a file-scoped namespace, a nested namespace is simply created.
Let's see a quick example 👇,
namespace Company.Product;
Company.Product.Component
namespace Component
{
}
It is clear that it is not a very big feature, but it is preferable that the more improvements there are, the easier and more intuitive the task of programming will be.
🔼 Primary constructors
In the latest released versions of C#, the topic of boilerplate code has been reduced considerably with features like automatic properties.
The main improvement of this is not simply reducing the amount of code that is written but reducing the amount of code that has to be read. It makes navigating code bases easier and reduces the most common places where errors can occur.
Primary constructors are a very good implementation that would again reduce the amount of code that is written. We can see it with this simple example that has a class that has a constructor and two read-only properties.
public class DataSlice
{
public string DataLabel { get; }
public float DataValue { get; }
public DataSlice(string dataLabel, float dataValue)
{
DataLabel = dataLabel;
DataValue = dataValue;
}
}
What the statistics tell us, is that 70% of its classes have constructors, and more than 90% of all of them simply do nothing more than copy parameters into properties.
If you haven't written any kind of constructor code yet, don't worry as we can still create and use the class in the same way.
var adultData = new DataSlice("Vaccinated adults", 741);
By using the main constructor, property validation is not excluded. In the same way, its rules can be enforced in a property setter.
Let's see an example 👇,
public class DataSlice(string dataLabel, float dataValue)
{
public string DataLabel
{
get => dataLabel;
set
{
if (value < 0) throw new ArgumentOutOfRangeException();
dataLabel = value;
}
}
public float DataValue { get => dataValue; }
}
Other details are also possible (calling the base constructor in a derived class, adding constructors). The main downside to all of this is that the primary constructors could collide with the position registers.
🔼 Raw string literals
We already know that the ordinary strings that C# has, tend to be quite messy since they need quotation marks (''), newlines (\ n), and backslashes (). What C# offers before this little problem is the use of special characters.
For example, we can prefix a string with @ and have free rein to add all these details without any problem 👇,
string path = "c:\\path\\backslashes";
string path = @"c:\pathh\backslashes";
What the raw string literal string allows is to create new paths to avoid escaping problems. Using the delimiter of a series of quotes followed by a line break to start, and a line break followed by the same number of quotes to close.
To understand it more simply, I leave you this example below 👇,
string xml = """
<part number="2021">
<name>year</name>
<description>this is the actual year
<ref part="2020">year</ref> actual year.
</description>
</part>
""";
If your concern is that there is a possibility of a triple quote sequence within the string, you can simply extend the delimiter so that you can use all the quotes you want, as long as the beginning and end are respected.
string xml = """"
Now """ is safe to use in your raw string.
"""";
In the same way as @ strings, newlines and whitespace are preserved in a raw string. What happens is that the common white space, that is, the amount that is used to bleed, is cut off.
Let's see more simply with an example 👇,
<part number="2021">
<name>year</name>
<description>this is the actual year
<ref part="2020">year</ref> actual year.
</description>
</part>
To this 👇,
<part number="2021">
<name>year</name>
<description>this is the actual year
<ref part="2021">year</ref> actual year.
</description>
</part>
With this, I want to explain to you that the raw strings are not intended to replace the @ strings that you are using right now.
Rather, they are prepared for the specific moments when you need a marked block or arbitrary code and in turn, you need a coding approach that is guaranteed to be safe.
🟢 Conclusion
To finish this article, Dotnetsafer conclusion is that C# still has many years of travel ahead of it and it still has many things to add to make the task of programming even easier and more optimal.
What do you think?🤔