C# 10 Record Structs
source link: https://medium.com/general-thoughts/c-10-record-structs-bf73353ed7bc
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
C# 10 Record Structs
C# 9 introduced a new type, record
, that was an immutable reference type with value equality. The new follow-up to this feature in C# 10 was the record struct
.
Without getting ahead of the content in this post too much, one of the new features of
record
in C# 10 is allowing the alias ofrecord class
to represent what we know of as a C# 9record
. Moving forward, I'll use this new naming to clarify which type I'm talking about.
This post discusses the new record struct
: explaining potential benefits and discussing the similarities as well as differences with record class
.
Features the record class
type
Before going into the similarities and differences of record class
and record struct
, lets discuss a few features of record class
types:
- Simple declaration syntax
- Reference semantics
- Value equality
- Readable ToString output
- Built-in deconstruction
- Inheritance hierarchies
- Immutability
What’s nice about this feature set is that you still have a lot of flexibility in your implementation. Here are a few examples:
- You can opt out of the simple syntax and write the record like a normal class.
- You can choose which properties are immutable and which aren’t.
- You can implement
IEquatable
and override the default equality implementation. - You can override the
ToString
implementation.
Tip: My book, C# Cookbook, has recipes that examine the inner workings and customize and extend
record
types.
As with class
and struct
types, you should expect that record class
and record struct
types have differences. That said, there are similarities and we'll look at those next.
Similarities of record class
and record struct
In simplified declaration syntax, value equality, ToString
overrides, and deconstruction; both record class
and record struct
are similar. The following sections show these similarities.
Simplified Declaration Syntax
Defining a classic class
or struct
involves multiple lines of code. As shown below, records streamline this syntax. The Place
record class
has an ID
and associated properties, consistent with common reference type usage. Along this train of thought, the Coordinates
record struct
only has a couple data properties (values), consistent with common value type usage:
Just like record class
types, record struct
follows modifiers with the type ID and parameter list. Notice that the parameter names use the PascalCasing naming convention because these parameters define the record's properties in addition to their role as constructor parameters.
Value Equality
Whether struct
or record struct
, value equality doesn't change, as shown below:
The code above creates two Place
record class
instances and two Coordinates
record struct
instances. Because of value equality, the results are true
for both comparisons. Changing a value in one of the instances would cause the comparison to be false
. Here's the output:
Place Tests
-----------
Equality: True
Coordinates Tests
-----------------
Equality: True
ToString
overrides
Since the beginning of .NET, the object
ToString
method has and continues to return the type name by default. In Visual Studio, this nearly worthless behavior means you have to drill down on a variable in a watch list to see the values you want. (To VS's credit, and a slight digression, the steady progression of debug visualization improvements have been useful). Nevertheless, the record class
improves on this situation by offering a default ToString
override with readable property values. The record struct
adopts this feature and the code below shows how it works:
The code above uses implicit ToString
calls through interpolated strings. The following output shows how readable this is for both record class
and now record struct
types:
Place Tests
-----------
ToString: Place { ID = ChIJ0X31pIK3voARo3mz1ebVzDo, Name = Las Vegas, NV }
Coordinates Tests
-----------------
ToString: Coordinates { Latitude = 36.17497, Longitude = -115.13722 }
Deconstruction
It’s understandable that classic C# developers might scratch their heads when looking at deconstruction in C#. However, if you’re accustomed to similar features in other programming languages, like tuples in Python and deconstructors in JavaScript, you might be a little more excited. In essence, a deconstructor in C# returns values of a type as a tuple. Here’s an example of how to use record
deconstructors:
Just like record class
types, record struct
type deconstruction returns values by position, corresponding to the order of property declaration. More specifically, in the example above, the Latitude
property is declared first and is assigned to the lat
variable in the resulting tuple. Now, instead of writing out coords1.Latitude
, you can just use lat
(or whatever other variable name you find acceptable to use).
Differences between record class
and record struct
The places where record class
and record struct
differ are semantics, inheritance, and immutability. The following sections describe these differences.
Semantics
Just like class
and struct
types, record class
and record struct
differ in reference semantics vs. value semantics, respectively. As mentioned earlier, a practical exception to reference vs. value type semantics is that default equality for a record class
type is value equality. All of the reasons for which you would use a struct
, you could also use a record struct
.
Inheritance
Related to semantics is the fact that record struct
types have interface inheritance, but record class
types have both implementation and interface inheritance. Here's how inheritance for record
types works:
As shown in the code above, inheritance syntax is the same as normal class
and struct
types.
An interesting aspect of record struct
types is the ease in which you can upgrade, as opposed to record class
types. A record class
type can only inherit another record class
. If a class
inherits a base class in a 3rd party library, you can't simply re-define it as a record class
. Because there are no such restrictions on interfaces, you can re-declare any struct
as a record struct
.
Immutability
This one might be a bit surprising — the default behavior of a record struct
is not immutability. By default, a record struct
is mutable (code can set its properties directly).
Tip: If ever there was a clue for a tricky interview question, this is it. Beware of the answer that relies on you to know that the default behavior of a
record struct
is mutable.
Fortunately, you can still have an immutable record struct
by using the readonly
modifier, shown below:
Per the definition of immutability, supported via the readonly
modifier, you can't directly change the properties of vegas
in the code above. The solution, just like record class
types is to use a with
expression, which creates a copy of vegas
with modified Longitude
.
Summary
This post introduces the C# 10 record struct
type via comparison with the record class
(aka the C# 9 record
type). You saw what was similar in the areas of type declaration, value equality, ToString
behavior, and deconstruction. There's also a discussion of what was different focusing on semantics, inheritance, and immutability.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK