C# 9 Preview

C# team will keep diving C# into the innovation world and adding more new features. The most C# 9 features are planned to be useful for all C# developers. The supported Microsoft .Net community will continue engaging us (C#Corner, Reddit, SO: Non-Microsoft developers) in the C# development process so that the compiler developers, can get early feedback for their code changes.
 
 
                                                                         Summarize Current State for us
 
Finally, Microsoft does not want to make C# driven by Community (Votes Driven), and they have left the final decision to the C# Design team.
I hope that Mads Torgersen can change this truth, and he can improve the connection between the different C# communities.
 
Important!
C# 9 ships with .Net 5. Records and DU are the main features for C# 9. Unfortunately, DU is moved from C# 9 plan to C# 10. It seems to be that Microsoft does not have enough resources to finish the initial plan.
  
C# 9 has two Core Concerns
  
 
1) Simplicity in common coding scenarios
2) More supporting the for the immutable data structures
 
C# 9 Core Concerns
 
Simplicity: Just make the thing simpler!
Immutability: More support for the Immutable data types like Records, read-only data structures, and empowering the pattern matching.
 
In this article, I will talk about the most C# 9 Candidates.
 
1. Nominal records
2. Records as a collection of features
3. Natural value equality
4. [Proposal] Improve overload resolution for delegate compatibility
5. Champion: relax ordering constraints around `ref` and `partial` modifiers on type declarations
6. Champion: Simplified parameter null validation code
7. Top level statements and member declarations (embrace scripting dialect)
8. Generators without language change
9. Champion: Module Initializers
10. Champion "Target-typed `new` expression"
11. Proposal: Target typed null coalescing (`??`) expression
12. Champion: target-typed conditional expression
13. Champion “Nullable-enhanced common type”
14. Champion “Permit attributes on local functions”
15. Champion “Covariant Return Types”
16. Champion “Records”
17. Primary Constructors
18. Champion "pattern-based `with` expressions"
19. Champion “Native-Sized Number Types”
20. Champion “and, or, and not patterns”
21. Proposal: allow comparison operators in switch case labels C#
22. Proposal: Use static keyword for lambdas to disallow capturing locals and parameters
23. Champion: Type Patterns
24. Champion “Lambda discard parameters”
25. Proposal: improvements to nullable reference types
26. Support for method argument names in nameof()
 
I will start with the most important feature for the C# 9
 
Records
Records can be of Value Type or Reference Type. Records are useful to represent complex data with many properties, like a database record or some model entity, DTO’s, etc.
 
•  Read-only properties          => Immutable Type
•  Equality implementations  => Structural Equality & Referential Equality
•  Pattern-matching support   => type pattern, switch pattern etc.
 
In my previous articles, I have described the Positional Records and the Nominal Records. If you are not familiar with those terms, don’t worry: I will simplify them as best I can. Basically, C# allows us to write the code in a positional or nominal style. Let us first take a look at the Object initializers.
 
Object initializers allows to create an object in a very flexible and readable format:
 
Microsoft Example:
  1. // The following code:  
  2. public class Person  
  3. {  
  4.   public string FirstName { getset; }  
  5.   public string LastName { getset; }  
  6. }  
  7.   
  8. // Can be created as follows  
  9. new Person  
  10. {  
  11.   FirstName = "Scott",  
  12.   LastName = "Hunter"  
  13. }  
  14. p.FirstName = “Alugili” // Works no Error!  
"The one big limitation today is that the properties have to be mutable for object initializers to work" Init-only properties fix that! 
  1. public class Person  
  2. {  
  3.   public string FirstName { get; init; }  
  4.   public string LastName { get; init; }  
  5. }  
  6. Person p = new Person  
  7. {  
  8.   FirstName = "Scott",  
  9.   LastName = "Hunter"  
  10. }  
  11. p.FirstName = “Alugili” // Error !  
Note: init-only properties are also useful to make individual properties immutable.
 
Positional Records and the Nominal Records
C# allows you to write the code with a positional or nominal code style. Object initializer belongs to the nominal category. Until C# 8, the nominal category is restricted because it required writable properties. The introduced “init” accessor removes this limitation in C# 9.
 
Nominal Records
Nominal data is defined as data that is used for naming or labeling variables.
 
Example: 
  1. data class User {string Name, int Age};  
  2. var user = new User {Name = "Bassam", Age= 43};  
  3. var copyUser = user with {Name = "Thomas"};  
Positional Records
The variables in ordinal data are listed in an ordered manner. 
  1. data class User(string Name, int Age);  
  2. var user = new User("Bassam", 43);  
  3. // Change the data  
  4. var copyUser = user with {Name = "Thomas"};  
  5. // Deconstruction  
  6. (string name, int age) = user;  
  7.   
  8. // Pattern matching (Type Pattern)  
  9. if (user is ("Bassam", _))  
  10. Console.Write($“My Name is {user.Name}”);  
Structural Equality & Referential Equality
Usually, records are compared by structure and not by reference. In C# 9 we can make both approaches.
 
Referential Equality (Identity)
Means that the pointers for two objects are the same when they have the same memory location, which leads us to the fact that pointers reference to the same object.
Identity: determines whether two objects share the same memory address.
 
Structural Equality
Means that two objects have equivalent content.
Equality: determines if two objects contain the same state.
 
Example:
  1. data class User (string name, int age);  
  2. public static void Main(){  
  3. User user = new User( "Bassam", 43);  
  4. User user2 = new User( "Bassam", 43);  
  5.   
  6. if (Equals(user, user2))  
  7. {  
  8.   Console.WriteLine("Structural Equality !");  
  9. }  
  10. if (ReferenceEquals(user, user2))  
  11. {  
  12.   Console.WriteLine("!!!! The code will not execute!!!!");  
  13. }  
Output:
Structural Equality!
 
Besides, Records support inheritance, which makes Records in C# unique and varies from the most functional programming languages and F#.
 
Inheritance in Records Example
  1. abstract data class User {string name, int age};  
  2. data class SuperUser:User {bool IsAdmin};  
  3. var user = new SuperUser(FirstName = "Bassam", Age= 43, IsAdmin true);  
Inheritance - Records mutation and “with” expression 
  1. // The runtime- type is preserved after the coping, consider the below example:  
  2. abstract data class User {string name, int age};  
  3. data class SuperUser:User {bool IsAdmin};  
  4.   
  5. var user = new SuperUser(Name = "Bassam", Age= 43,IsAdmin true);  
  6. var copyUser = user with {Name = "Thomas"};  
  7. Console.WriteLine(copyUser.GetType().Name); // Output: SuperUser  
Improve overload resolution for delegate compatibility
This feature allows the below code to compile: 
  1. delegate void MyAction<T>(T x);  
  2.   
  3. void Y(long x) { }  
  4.   
  5. void D(MyAction<int> o) { }  
  6. void D(MyAction<long> o) { }  
  7.   
  8. void T()  
  9. {  
  10.   D(Y); // Ambiguous between both D calls, despite the fact that `void   
  11.   D(MyAction<int>)` is not a valid target.  
  12. }  
Presently, this code is not compiled because the compiler cannot determine which ’D’ method should be applied.
 
Relax ordering of ref and partial modifiers
Allows the partial keyword before ref in the class definition.
 
Example: 
  1. public ref partial struct {} // C# 7  
  2. public partial ref struct {} // C# 9  
Champion: Simplified parameter null validation code
Allows simplifying the standard null validation on parameters by using a small annotation on the parameters.
 
Example: 
  1. // Before C# 1..7.x  
  2. void DoSomething(string txt)  
  3. {  
  4.   if (txt is null)  
  5.  {  
  6.    throw new ArgumentNullException(nameof(txt));  
  7.  }  
  8.   …  
  9. }  
  10.   
  11. // Candidate for C# 9  
  12. void DoSomething (string txt!)  
  13. {  
  14.   …  
  15. }  
Top-level statements and member declarations
This feature allows you to write directly C# code in the file without any overhead, just like scripting languages. It is useful for the C# beginner and in some use cases.
 
C# 8 
  1. public static class Program   
  2. {  
  3.   public static void Main()   
  4.   {  
  5.     System.Console.WriteLine("Huhu everything is not gone!");  
  6.   }  
  7. }  
Output:
Huhu, everything is not gone!
 
C# 9
  1. System.Console.WriteLine("Huhu everything is gone!");  
Output:
Huhu, everything is gone!
 
Module Initializers
The module initializer code is executed when an assembly is Loaded/ Initialized. You can compare it with the static constructor in C#, but in the case of module initializers, the method is executed only once for the entire assembly.
 
Example: 
  1. module: System.Runtime.CompilerServices.ModuleInitializerAttribute(typeof(MyModuleInitializer))]  
  2. internal static class MyModuleInitializer  
  3. {  
  4.   static MyModuleInitializer()  
  5.   {  
  6.     // Put your module initializer here! This code will execute one time when the assembly is Initialized.  
  7.   }  
  8. }  
Info from the proposal comments:
 
1.1.2.1 Module Initialization Guarantees
The CLI shall provide the following guarantees regarding module initialization:
 
1. The semantics of when, and what triggers execution of module initialization methods, is as follows:
     A. A module may have a module initializer method, or not.
B. The module’s initializer method is executed at, or sometime before, first access to any types, methods, or data defined in the module
2. A module initializer shall run exactly once for any given module, unless explicitly called by user code
3. No method other than those called directly or indirectly from the module initializer will be able to access the types, methods, or data in a module before its initializer  completes execution.
 
Enhancing the Common Type Specification
 
Target typed null coalescing (`??`) expression
It is about allowing an implicit conversion from the null coalescing expression.
 
Example: 
  1. void M(List<int> list, uint? u)  
  2. {  
  3.   IEnumerable<int> x = list ?? (IEnumerable<int>)new[] { 1, 2 }; // C# 8  
  4.   var l = u ?? -1u; // C# 8  
  5. }  
  6.   
  7. void M(List<int> list, uint? u)  
  8. {  
  9.   IEnumerable<int> x = list ?? new[] { 1, 2 }; // C# 9  
  10.   var l = u ?? -1; // C# 9  
  11. }  
Nullable-enhanced common type and the Target-typed conditional
Permits an implicit conversion from the conditional expression to any type. 
  1. int? result = true ? 0 :(int?) null// C# 8  
  2. int? result = true ? 0 : null// C# 9 implicitly converted  
  3. Animal a = Reptile ?? snake; // C# 9 the implicitly converted to the shared base  
Permit ternary operation with int? and double operands
Spec changes: This is about to permit the following syntax: 
  1. int? ni = 1;  
  2. double d = 4;  
  3. bool c = true;  
  4. var x1 = c ? ni : d;  
The above code will produce the following error(C# 8):
error CS0173: Type of conditional expression cannot be determined because there is no implicit conversion between ‘int?’ and ‘double’
 
Attributes on local functions
 
Covariant Return Types
Covariant return types is a feature in which a subclass of a type can be specified in a method override in a derived type; In other words, the overridden method has a more specific return type than the declaration in the base type.
 
Microsoft Example:
  1. abstract class Animal  
  2. {  
  3.   public abstract Food GetFood();  
  4. }  
  5. class Tiger : Animal  
  6. {  
  7.   public override Meat GetFood() => ...;  
  8. }  
At first glance, this feature seems to be useless until it is put to some good use, and then, it helps to get rid of a bunch of ugly code.
Personally, I find it a very nice feature, and finally, I can write overridden Clone methods that return the derived type.
 
Primary Constructors
Primary constructors reduce some code overhead by putting constructor arguments directly after the class name. 
  1. // C# 1..8  
  2. class User  
  3. {  
  4.   private string name;  
  5.   
  6.   public User(string name)  
  7.   {  
  8.     this.name = name;  
  9.   }  
  10.   
  11.   public string Name  
  12.   {  
  13.     get => this.name;  
  14.     set { this.name = value; }  
  15.   }  
  16. }  
  17.   
  18. // C# 9  
  19. class User(string name)  
  20. {  
  21.   private string name;  
  22.   
  23.   public string Name  
  24.   {  
  25.     get => this.name;  
  26.     set { this.name = value; }  
  27.   }  
  28. }  
Native Ints
Introduces a new set of native types (nint, nuint). The ‘n’ is standing for native. The design of the new data types is planned to allow a one C# source file to use 32 naturally- or 64-bit storage depending on the host platform type and the compilation settings.
 
Example:
The native type is depending on the OS, 
  1. nint nativeInt = 55; take 4 bytes when I compile in 32 Bit host.  
  2. nint nativeInt = 55; take 8 bytes when I compile in 64 Bit host with x64 compilation settings.  
Pattern matching Improvements and the switch expression
 
Type Patterns
Lambda discard parameters
allow comparison operators in switch case labels
Champion “and, or, and not patterns
Champion "pattern-based `with` expressions 
 
Here you can find my Introduction to C# 9 pattern matching:
 
More Pattern Matching Examples:
  1. static void Main()  
  2. {  
  3.   TypePatterns(1, string.Empty);  
  4.   Console.WriteLine(InYearRange(2005));  
  5. }  
  6.   
  7. // Type Patterns  
  8. static void TypePatterns(object par1, object par2)  
  9. {  
  10.   var t = (par1, par2);  
  11.   if (t is (intstring)) // test if par1 is an int and par2 is a string  
  12.       Console.WriteLine("Type Pattern found");  
  13.   
  14.  switch (par1)  
  15.  {  
  16.     case int:  
  17.     Console.WriteLine($"par1 value: {par1}");  
  18.     break// test if par1 is an int  
  19.     _:  
  20.     break// Discard  
  21.   }  
  22. }  
  23.   
  24. // Relational Patterns  
  25. public static int InYearRange(int year)  
  26. {  
  27.   return year switch  
  28.   {  
  29.     < 2000 => 2000,  
  30.     <= 2020 => 2020 ,  
  31.     _ => 0  
  32.   };  
  33. }  
  34.   
  35. // Pattern Combinators - and, or, not  
  36. static bool IsLetter(char? @char)  
  37. {  
  38.   if (@char is not null)  
  39.   {  
  40.     return @char is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');  
  41.   }  
  42.   return false;  
  43. }  
Output:
Type Pattern found
par1 value: 1
2020
 
Summary
In this article, I have covert nearly all C# 9 Champions Canadians, and I hope that my work will make you better prepared for the next C# version.
 
Thank you! I appreciate you for reading my article, and I recommend you to watch my video on YouTube about C# 10 and the Discriminated Unions.
 
Reviewers:
Niloshima Srivastava, well, thanks for taking your time; I really appreciate it. See you soon at the C# Conference.
You can follow me on twitter: https://twitter.com/bassamalugili
or my homepage: www.bassam.ml


Similar Articles