The official planned next C# release is C# 9. You can find in this link the Language Version Planning for C#·
As shown above, in the list, there are 27 proposals/features are planned for C# 9, but that does not mean that all of those proposals will be released in C# 9.
Which proposal will be released, and in which version? The .Net development team can only answer all these questions. They have the final decision, and they can change their minds all the time, both on proposed features and syntax/semantics of these features.
The most important features planned for C# 9, namely record types, unfortunately Discriminated Unions is moved to C# 10 plan, more pattern matching enhancements, and additional target-typing of existing constructs such as ternary and null-coalescing expressions.
In this article, I will describe Record and Discriminated Unions and the other proposals I will describe them briefly.
!!!!! Important !!!!!
Particularly for both Records and Discriminated Unions, they are not in the final stage. They both still need a lot of work to move from the current strawman proposal syntax to what their final designs will be.
Records
I have been waiting for a long time for this feature. Records are a lightweight type. They are nominally typed, and they might have (methods, properties, operators, etc.), and allow you to compare structural equality! Also, the record properties are read-only by default. Records can be Value Type or Reference Type. Records are useful to represents a 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
- Pattern-matching support = is pattern, switch pattern etc.
The Proposal in GitHub here.
The most up-to-date proposal for records is here.
In my previous articles, I have described the Positional Records and this article I will talk about 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:
- // The following code:
- public class Person
- {
- public string FirstName { get; set; }
- public string LastName { get; set; }
- }
- // Can be created as follows
- new Person
- {
- FirstName = "Scott",
- LastName = "Hunter"
- }
- 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!
- public class Person
- {
- public string FirstName { get; init; }
- public string LastName { get; init; }
- }
- Person p = new Person
- {
- FirstName = "Scott",
- LastName = "Hunter"
- }
- 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:
- data class User {string Name, int Age};
- var user = new User {Name = "Bassam", Age= 43};
- var copyUser = user with {Name = "Thomas"};
Positional Records
The variables in ordinal data are listed in an ordered manner.
- data class User(string Name, int Age);
- var user = new User("Bassam", 43);
- // Change the data
- var copyUser = user with {Name = "Thomas"};
- // Deconstruction
- (string name, int age) = user;
- // Pattern matching (Type Pattern)
- if (user is ("Bassam", _))
- 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:
- data class User (string name, int age);
- public static void Main(){
- User user = new User( "Bassam", 43);
- User user2 = new User( "Bassam", 43);
- if (Equals(user, user2))
- {
- Console.WriteLine("Structural Equality !");
- }
- if (ReferenceEquals(user, user2))
- {
- Console.WriteLine("!!!! The code will not execute!!!!");
- }
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
- abstract data class User {string name, int age};
- data class SuperUser:User {bool IsAdmin};
- var user = new SuperUser(FirstName = "Bassam", Age= 43, IsAdmin true);
Inheritance - Records mutation and “with” expression
- // The runtime- type is preserved after the coping, consider the below example:
- abstract data class User {string name, int age};
- data class SuperUser:User {bool IsAdmin};
- var user = new SuperUser(Name = "Bassam", Age= 43,IsAdmin true);
- var copyUser = user with {Name = "Thomas"};
- Console.WriteLine(copyUser.GetType().Name); // Output: SuperUser
Nominal Records in Depth
The Nominal Records can be defined as follows:
- data class Point3D
- {
- int X,
- int Y,
- int Z,
- }
Equivalent to:
The proposed solution is a new modifier, init, that can be applied to properties and fields,
- class Point3D
- {
- int X { get; init;}
- int Y { get; init;}
- int Z { get; init;}
- ...
- ...
- }
Creating Record:
- void DoSomething()
- {
- var point3D = new Point3D()
- {
- X = 1,
- Y = 1,
- Z =1
- };
- }
Record from the old proposal (Positional)
Example, the following record with a primary constructor
- data class Point3D(int X, int Y, int Z);
Would be equivalent to:
- public class Demo
- {
- public void CreatePoint()
- {
- var p = new Point3D(1.0, 1.0, 1.0);
- }
- }
Would be equivalent to:
- data class Point3D
- {
- public int X { get; }
- public int Y { get; }
- public int Z { get; }
- public Point(int x, int y, int z)
- {
- X = x;
- Y = y;
- Z = z;
- }
- public void Deconstruct(out int X, out int Y, out int Z)
- {
- X = this.X;
- Y = this.Y;
- Z = this.Z;
- }
- }
The final generation of the above would be:
- class Point3D
- {
- public initonly int X { get; }
- public initonly int Y { get; }
- public initonly int Y { get; }
- public Point3D(int x, int y, int z)
- {
- X = x;
- Y = y;
- Y = z;
- }
- protected Point3D(Point3D other)
- : this(other.X, other.Y, other.Z)
- { }
- [WithConstructor]
- public virtual Point With() => new Point(this);
- public void Deconstruct(out int X, out int Y, out int Z)
- {
- X = this.X;
- Y = this.Y;
- Z = this.Z;
- }
- // Generated equality
- }
Using Records and the With-expression
Records proposal is introduced with the new proposed feature "with-expression". In programming, an immutable object is an object whose state cannot be modified after it is created. If you want to change the object you have to copied it. The “with” help you to solve the problem, and you can use them together as the following.
- public class Demo
- {
- public void DoIt()
- {
- var point3D = new Point3D() { X = 1, Y = 1, Z =1 };
- Console.WriteLine(point3D);
- }
- }
- var newPoint3D = point3D with {X = 42};
The created new point (newPoint3D) just like the existing one (point3D), but with the value of X changed to 42.
This proposal is working very well with pattern matching.
Records in F#
Copy from F# MSDN example, type Point3D = {X: float; Y: float; Z: float}
- let evaluatePoint (point: Point3D) =
- match point with
- | { X = 0.0; Y = 0.0; Z = 0.0 } -
- > printfn "Point is at the origin."
- | { X = xVal; Y = 0.0; Z = 0.0 } -
- > printfn "Point is on the x-axis. Value is %f." xVal
- | { X = 0.0; Y = yVal; Z = 0.0 } -
- > printfn "Point is on the y-axis. Value is %f." yVal
- | { X = 0.0; Y = 0.0; Z = zVal } -
- > printfn "Point is on the z-axis. Value is %f." zVal
- | { X = xVal; Y = yVal; Z = zVal } -
- > printfn "Point is at (%f, %f, %f)." xVal yVal zVal
- evaluatePoint { X = 0.0; Y = 0.0; Z = 0.0 }
- evaluatePoint { X = 100.0; Y = 0.0; Z = 0.0 }
- evaluatePoint { X = 10.0; Y = 0.0; Z = -1.0 }
The output of this code is as follows.
Point is at the origin.
Point is on the x-axis. Value is 100.000000.
Point is at (10.000000, 0.000000, -1.000000).
Record types are implemented by the compiler, which means you have to meet all of those criteria and can't get them wrong.
So not only do they save a lot of boilerplate, they eliminate an entire class of potential bugs.
Moreover, this feature existed over a decade in F#, and other languages like (Scala, Kotlin) have a similar concept too.
Examples for other languages that support both constructors and records.
F#
- type Greeter(name: string) = member this.SayHi() = printfn "Hi, %s" name
Scala
- class Greeter(name: String)
- {
- def SayHi() = println("Hi, " + name)
- }
Equality
Records are compared by structure and by reference
Example
- void DoSomething()
- {
- var point3D1 = new Point3D()
- {
- X = 1,
- Y = 1,
- Z =1
- };
- var point3D2= new Point3D()
- {
- X = 1,
- Y = 1,
- Z =1
- };
- var compareRecords = point3D1 == point3D2; // true, operator overload or with Equal method
- }
Discriminated Unions (C# 10)
The term Discriminated Unions (disjoint union) is borrowed from mathematics. A simple example to understand the term.
Below the Venn diagram is shown(disjoint union) by two non-overlapping closed regions and said inclusions are shown by showing one closed curve lying entirely within another.
Two sets A and B are said to be disjoint, if they have no element in common.
Thus, A = {1, 2, 3} and B = {5, 7, 9} are disjoint sets; but the sets C = {3, 5, 7} and D = {7, 9, 11} are not disjoint; for, 7 is the common element of A and B.
Two sets A and B are said to be disjoint, if A ∩ B = ϕ. If A ∩ B ≠ ϕ, then A and B are said to be intersecting or overlapping sets.

Bassam AlugiliPosted Aug 22, 2020, 2:44 AM
Thank you Michel, I will forward your comments to the editor!
Michel ComeauPosted Aug 21, 2020, 2:02 PM
This is really great but wanted to mention a possible typo .. one of your class point3d has XYY properties instead of XYZ
Aslan KystaubayevPosted May 23, 2020, 7:58 PM
Good article, thanks
Raju PaladiyaPosted Feb 11, 2020, 12:56 PM
Wow Great !!
Jefferson S. MottaPosted Feb 4, 2020, 6:48 AM
These changes will make c# easier. Good article.
Ankur MistryPosted Jan 1, 2020, 9:04 PM
Wow nicely explained
Bassam AlugiliPosted Dec 19, 2019, 5:50 AM
Thank you Zhenyu Liu to open this good discussion. I will post it here to help the other developers: https://www.reddit.com/r/csharp/comments/eaiyif/some_questions_about_the_proposed_c_9_features
Bassam AlugiliPosted Dec 11, 2019, 6:26 AM
HN about the article. some of the comments are really good: https://news.ycombinator.com/item?id=21719949
Zhenyu LiuPosted Dec 9, 2019, 6:14 PM
Wow, awesome
Bassam AlugiliPosted Dec 6, 2019, 3:57 PM
Thank you Mahesh :)
Mahesh ChandPosted Dec 6, 2019, 3:43 PM
Nicely done. Thank you Bassam Alugili for looking ahead and covering v 9 of C#.
Sourav Kumar DasPosted Dec 6, 2019, 6:29 AM
Nice article Sir.
Bassam AlugiliPosted Dec 6, 2019, 4:27 AM
Thank you too Rushi!
Rushi MehtaPosted Dec 6, 2019, 3:04 AM
Thanks for sharing, really something new features in explained
Bassam AlugiliPosted Dec 6, 2019, 1:45 AM
Thank You Faisal :-)
Faisal PathanPosted Dec 6, 2019, 12:20 AM
Excellent article, some are very new features for me, Thanks for sharing!