2

C# 10 Record Structs

 2 years ago
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.
neoserver,ios ssh client

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 of record class to represent what we know of as a C# 9 record. 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.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK