24
List of C# 9 features
The very simple program on C# looks like the following
using System;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
but with C# 9 we can make it simpler:
System.Console.WriteLine("Hello World!");
Let's imagine we have a class:
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
public Book()
{
}
public Book(string title, string author)
{
Title = title;
Author = author;
}
}
Usually we create objects like follow:
var book = new Book();
// or
Book book = new Book();
With C# we can do:
Book book2 = new();
Book book3 = new("1", "A1");
Consider we have a class Book
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
}
and we can set values during the initialization. At the same time we can change the values as far as we have a setter for every field as well.
var book = new Book { Author = "1", Title = "2" };
book.Title = "2";
Imagine a situation where we want only set values during the initialization and we want to restrict them after initialization. In C# 9
Init-only
feature comes into play:
public class Book
{
public string Title { get; init; }
public string Author { get; init; }
}
var book = new Book { Author = "1", Title = "2" };
book.Title = "2"; // compile error
Relational patterns permit the programmer to express that an input value must satisfy a relational constraint when compared to a constant value
public class Book{ public string Title { get; set; } public string Author { get; set; } public static decimal Postage(decimal price) => price switch { < 20 => 6.99m, >= 20 and < 40 => 5.99m, >= 40 and < 60 => 2.99m, _ => 0 };}
We have a class:
public class Book{ public string Title { get; } public string Author { get; } public Book(string title, string author) { Title = title; Author = author; }}
And let's imagine we want to be able to create a book. We can do it as following:
var book = new Book("Title1", "Author1");
and also we want to serialize the object and de-serialize
var json = JsonSerializer.Serialize(book);Console.WriteLine(json);var book2 = JsonSerializer.Deserialize<Book>(json);var isEqual = book == book2;Console.WriteLine($"book == book2: {isEqual}"); // false
In console we'll see that de-serialized book is not the same book that was serialized. How can we make them the same? We can override equals operator:
public static bool operator ==(Book left, Book right) => left is object ? left.Equals(right) : right is null;
if we override ==
operator we also have to override !-
operator:
public static bool operator !=(Book left, Book right) => !(left == right);
But we have not done anything to compare our object yet, so we have to override Equals
method:
public override bool Equals(object obj) => obj is Book b && Equals(b);public bool Equals(Book other) => other is object && Title == other.Title && Author == other.Author;
And we need to override GetHashCode
and ToString
methods.
public override int GetHashCode() => HashCode.Combine(Title, Author);public override string ToString() => $"{Title} - {Author}";
Also, we have to make our class implement IEquatable<T>
interface.
public class Book : IEquatable<Book>
Finally we have to write a bunch of code for just one simple action. Full class:
public class Book : IEquatable<Book>{ public string Title { get; } public string Author { get; } public Book(string title, string author) { Title = title; Author = author; } public static bool operator ==(Book left, Book right) => left is object ? left.Equals(right) : right is null; public static bool operator !=(Book left, Book right) => !(left == right); public override bool Equals(object obj) => obj is Book b && Equals(b); public bool Equals(Book other) => other is object && Title == other.Title && Author == other.Author; public override int GetHashCode() => HashCode.Combine(Title, Author);}
Now Console.WriteLine($"book == book2: {isEqual}");
will output true
.
There is a lot of boilerplate code.
Moreover, if we add a new field we will have to update every method.
With C# 9
we can use the record
type for the same behavior. It allows making behavior for classes as if they were structures.
record Book(string Title, string Author)
Now we can use modifiers and return values for partial methods.
public partial class Book{ public string Title { get; set; } public string Author { get; set; } public decimal Price { get; set; } private partial decimal SetPrice();}public partial class Book{ private partial decimal SetPrice() { return 0m; }}
In C# 9
we can return derived types in overridden methods.
public class Book{ public string Title { get; set; } public string Author { get; set; }}public class CollectionBook : Book{ public string Edition { get; set; }}public abstract class BookService{ public abstract Book GetBook();}public class CollectionBookService : BookService{ public override CollectionBook GetBook() { return new CollectionBook(); }}
Cheers!
24