C# 9 Cheat Sheet

C# 9 Cheat Sheet with code example and pros and cons.
 
You can download it from GitHub as PDF or Powerpoint.
 
Do not forget to watch and start the project to get the lastest cheat updates. 
 
 

Records

 
A new C# type that is immutable by default. The equality between Records is compared by structure or by reference.
  1. // Default Record  
  2. public record Person(string Name, int Age);  
  3. // Mutable Record  
  4. public record Person(string Name, int Age)  
  5. {  
  6.    public string Name { getset; } = Name;  
  7.    public int Age { getset; } = Age;  
  8. }  
  9. var person1 = new Person("Bassam Alugili",42 );    
  10. var person2 = new Person("Bassam Alugili",42);    
  11.     
  12. Console.WriteLine(person1 == person2); // True; Structural equality    
  13. Console.WriteLine(person1.Equals(person2)); // True; Structural equality    
  14. Console.WriteLine(ReferenceEquals(person1, person2)); // False; Referential equality    
  15.     
  16. var person3 = person1 with { Age = 43 }; // Change the default record! --> Create a new one!    
  17. var (name, age) = person3; // Destruct a record.    
  18.     
  19. Console.WriteLine(person1 == person3); // False; Structural equality    
  20.     
  21. // Change the mutable record.    
  22. person1.Age = 43;
Pros
 
Records are a lightweight type that can remove much code.
 
Structural equality and Referential equality.
 
Cons
 
Allocating many objects.
 

Pattern Matching Enhancements

 
C# 9 contains six main changes in pattern matching. Type patterns enhancements and all other patterns are introduced in C# 9.
 
Type pattern is used to match the input against a type. If the input type is a match to the type specified in the pattern, the match succeeds.
 
C# 9 removes the type pattern followed by another pattern restriction.
 
Parenthesized patterns permit the programmer to put parentheses around any pattern.
 
Relational patterns permit the programmer to match their input against constant values to determine if the input is > or < or = to that constant.
 
Combinator Patterns Permit the programmer to combine multiple patterns on one line with AND/OR operators or negate a pattern using the NOT operator.
  • Conjunctive patterns represent the logical “and” of the two sub-patterns, pattern1 and pattern2.
  • Disjunctive patterns represent the logical “or” of the two sub-patterns, pattern1 and pattern2.
  • Negated “not” patterns that requires a given pattern not to match.
Type Patterns
  1. object checkType = new int();  
  2. var getType = checkType switch  
  3. {  
  4.   string  
  5.   => "string"// In C# 9 you can match against the type only.  
  6.   int => "int",  
  7.   _ => "obj"  
  8. };  
  9. Console.WriteLine(getType); // Output: int  
Or patterns
  1. var person = new Person("Bassam", 43);  
  2. var ageInRange = person switch  
  3. {  
  4.    Person(_, < 18) => "less than 18",  
  5.    (_, 18) or (_, > 18) => "18 or greater"  
  6. };  
  7. Console.WriteLine(ageInRange); // Output: 18 or greater  
And patterns
  1. var person = new Person("Bassam", 43);  
  2. var ageInRange = person switch  
  3. {  
  4.   Person(_, < 18) => "less than 18",  
  5.   ("Bassam", _) and (_, > 18) => "Bassam is greater than 18"  
  6. };  
  7. Console.WriteLine(ageInRange); // Output: Bassam is greater than 18  
Negated not patterns
  1. var person = new Person("Bassam", 43);  
  2. var meOrNot = person switch  
  3. {  
  4.   not ("Bassam", 43) => "not me!",  
  5.   _=>"Me :-)"  
  6. };  
  7. Console.WriteLine(meOrNot);  
Parenthesized patterns 
  1. public record IsNumber(bool Isvalid, int Number);  
  2. var is10 = new IsNumber(true, 10);  
  3. var n10 = is10 switch  
  4. {  
  5.   ((_, > 1 and < 5) and (_, > 5 and < 9)) or (_, 10) => "10",  
  6.   _=> "not 10"  
  7. };  
  8. Console.WriteLine(n10); // Output: 10  
Relational Patterns
  1. var person = new Person("Bassam", 43);  
  2. var person2 = new Person("Thomas", 4);  
  3. var ageInRange = person switch  
  4. {  
  5.    Person(_, < 18) => "less than 18"// Note: the type Person is defined.  
  6.    (_, > 18) => "greater than 18"// Note: the type Person is inferred.  
  7.    (_, 18) => "18 years old!"  
  8. };  
  9. Console.WriteLine(ageInRange); // Output: greater than 18  

Native Ints

 
C# 9 adds two new data types (nint, nuint). The new types are depending on the host platform and the compilation settings
  1. nint nativeInt = 55;  
  2. Console.WriteLine(nint.MaxValue);  
Compiled in x86 Configuration:
Output: 2147483647
 
Compiled in x64 Configuration:
Output: 9223372036854775807
 
Pros
 
Make C# more compatible with Mac and iOS APIs.
 
Cons
 
A lot of C# developers are not familiar with this concept. 
 

Init Only Setters

 
This feature allows you to create an object in the nominal code style. Object initializer belongs to the nominal category. 
  1. public class InitDemo  
  2. {  
  3.   public string Start { get; init; }  
  4.   public string Stop { get; init; }  
  5. }  
  6. // Creating the object with the nominal style.  
  7. var initDemo = new InitDemo  
  8. {  
  9.   Start = "S1",  
  10.   Stop = "S2"  
  11. };  
Pros
 
Nice syntax, and it removes some code overhead.
 

Target New Types

 
This feature allows you to omit the type of object you are instantiating. 
  1. Point p = new(1, 1);  
  2. ConcurrentDictionary<intint> dict = new();  
  3. Point[] ps = { new(1, 1), new(2, 2), new(3, 3) };  
Pros
 
Less code.
 
Cons
 
It can make your code harder to read. 
 

Target-typed Conditional Expressions 

 
This feature allows you the implicit conversion from the null coalescing expression. 
  1. void M( int[] list, uint? u)  
  2. {  
  3.   int[] x = list ?? (int[]) new[] { 1, 2 }; // C# 8  
  4.   var l = u ?? -1; // C# 9 in C# 8 you need -1u  
  5. }  
Localsinit
 
In C# 9, you can use the new attribute SkipLocalsInit to instruct the compiler to suppress the emitting .locals init flag. This attribute applies at the module, class, or method level.
  1. [System.Runtime.CompilerServices.SkipLocalsInit]  
  2. static unsafe void DemoLocalsinit()  
  3. {  
  4.   int x;  
  5.   Console.WriteLine(*&x); // Take care! x is not initialized!  
  6. }  
Pros
 
Improve the performance of the method.
 
Cons
 
The impact on the method performance in most cases is small. Please use it only if you know exactly what you are doing! And with profiling.
 

Static Anonymous Functions

 
This feature allows you to use the static keyword for lambdas to prevent capturing locals and parameters. 
  1. public int number = 5;  
  2. ….  
  3. Func<string> toString = () => number.ToString();  
  4. Console.WriteLine(toString());  
The field number is captured by the anonymous lambda function and can cause an unintended allocation. To solve that, you can add the modifier static on the lambda and use the const modifier on the field (number). 
  1. public const int number = 5;  
  2. ….  
  3. Func<string> toString =static () => number.ToString();  
  4. Console.WriteLine(toString());  
Pros
 
Anonymous methods need allocations, and this feature might help to make anonymous methods more performant.
 

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.
  1. public virtual Person GetPerson()  
  2. {  
  3.   // This is the parent (or base) class  
  4.   return new Person();  
  5. }  
  6. public override Person GetPerson()  
  7. {  
  8.   // You can return the child class, but it still returns a Person  
  9.   return new Student();  
  10. }  
  11.   
  12. // Now, you can return the more specific type in C# 9.  
  13. public virtual Person GetPerson()  
  14. {  
  15.   // This is the parent (or base) class  
  16.   return new Person();  
  17. }  
  18. public override Student GetPerson()  
  19. {  
  20.   // Better!  
  21.   return new Student();  
  22. }  
Pros
 
It can help you to remove a lot of ugly typecasting.
  

Extension GetEnumerator support for foreach loops

 
This feature allows you to create an extension method to allow foreach loops on IEnumerator<T> and IAsyncEnumerator<T> interfaces. 
  1. public static class Extensions  
  2. {  
  3.   public static IEnumerator<T> GetEnumerator<T>(this IEnumerator<T> enumerator) => enumerator;  
  4. }  
With the above extension, you can do: 
  1. IEnumerator<string> enumerator = new Collection<string> { "Bassam""Thomas""Volker" }.GetEnumerator();  
  2. foreach (var guru in enumerator)  
  3. {  
  4.   Console.WriteLine($"Welcome {guru}!");  
  5. }   

Lambda Discard Parameters

 
This feature allows the lambda to have multiple declarations of the parameters named _. In this case, the parameters are "discards" and are not usable inside the lambda.
Allow discards (_) to be used as parameters of lambdas and anonymous methods.
  1. Func<int,int,int> zero = (_,_) => 0;  
Pros
 
Syntax Sugar.
 

Attributes on Local Functions

 
The idea is to permit attributes to be part of the declaration of a local function. 
  1. static void Main(string[] args)  
  2. {  
  3.   // Attribute!  
  4.   [Conditional("DEBUG")]  
  5.   static void DoSomething([NotNull] string test) // NotNull-attribute  
  6.   {  
  7.     Console.WriteLine("Do it!");  
  8.   }  
  9.   DoSomething ("test");  
  10.   
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. 
  1. [ModuleInitializer]  
  2. public static void DoSomethingBeforeMain()  
  3. {  
  4.   Console.WriteLine(“Huhu”);  
  5. }   
Extending Partial Methods
 
This feature allows you to remove the restrictions of the partial method (void, out, accessibility).
  1. partial class Doing  
  2. {  
  3.  internal partial bool DoSomething(string s, out int i);  
  4. }  
  5.   
  6. partial class Doing  
  7. {  
  8.   internal partial bool DoSomething(string s, out int i)  
  9.   {  
  10.     i = 0;  
  11.     return true;  
  12.   }  
  13. }   
Function Pointers
 
This feature allows you to use delegate* for the declaration of function pointers.
  1. unsafe class FunctionPointer  
  2. {  
  3.   static int GetLength(string s) => s.Length;  
  4.   
  5.   delegate*<stringint> functionPointer = &GetLength;  
  6.   
  7.   public void Test()  
  8.   {  
  9.     // Output: 4  
  10.     Console.WriteLine(functionPointer("test"));  
  11.   }  
  12. }