The Ultimate Guide to C# Records

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Records have been added in C# 9 and 10. They have already become a mainstay,   that much is clear. So, are you using records in your designs, then?  Do you need any clarifications? Alright, stay with me, and I will   clarify the use of records in C#. I promise, before this video ends,   you will know your records inside and out. You will know how they work and how to utilize   them in your object-oriented  and functional designs.  Let's get straight to code. What does this line of code say?  Quite formally, it defines an immutable class with  two read-only properties, FirstName and LastName,   and with value-typed semantics. You can use this class whenever   a value object is needed. And that is the whole story.  If you know what I just said, then you can go  and watch other videos on my channel because   that is all there is in records, seriously. But if that still puzzles you, and you want   to see what are the internals of  this record, then stay with me.  I will now implement this same record by hand. It is a class like any other.  The compiler will synthesize two  properties from the primary constructor.  That is what it does when it  sees the record declaration.  The properties will be read-only,  but with init-only setters on them.  You can also use primary constructors on  your custom classes starting with C# 12.  There is no magic in them. It is a constructor like any other,   with a few twists you might want to be aware of. Watch my other video explaining how you can use   primary constructors correctly, while avoiding  the possibility of making bugs in that way.  There's a link in the description as well. So, the record has a constructor that initializes   the two mandatory read-only public properties. It also supports the so-called   non-destructive mutation. To change an instance,   you will create a new instance and  list the properties you want to change.  Records support non-destructive  mutation via the with expressions   and those require a copy constructor. So, you can do the same thing.  You can also support  non-destructive mutation in your   custom classes by supplying a copy constructor. Then, create a new instance from this one, set the   desired properties to new values, and that's it. That is non-destructive   mutation in a custom class. Records also support deconstruction.  What you see in the primary constructor  are the so-called positional properties.  You can assign a record to a tuple literal,  consisting of variables in the same positional   order as in the primary constructor. And you can use discard if you   want to ignore some components. A custom class can define a special   Deconstruct method to achieve the same effect. Its work is opposite to the constructor's work.  It's filling out the output variables this time. With this, we have covered immutability,   construction, and deconstruction of records. Records also implement equivalence.  They implement the strongly typed IEquatable  interface for performance reasons.  The general Equals just forwards the call. Everything is so straightforward at this point.  But the story does not end here. If you forgot equality and   inequality operator overloads, you  have just added a bug to your class.  Fortunately, the compiler will never forget  to synthesize these two operators as well.  Last but not least, the compiler will also  synthesize a convenient ToString overload.  Look what I have got - about  20 lines of code and it is all   condensed into a single-line declaration. I have coded hundreds of value objects in my   past projects and now that we have got records  in C# I see no point in doing that ever again.  But this is far from all. Oh, records are much more than this one line.  A record is a class. Therefore, it can derive from another record.  Can you imagine the complexity of changes  this modification has made under the hood?  From the outside, instantiating one  or the other record looks the same.  The difference is in the code the compiler  has synthesized under the declaration.  If I gave you the task to implement these  two classes, one deriving from the other,   still maintaining value-typed semantics, would  you be able to do that without introducing bugs?  Watch and learn. The derived class will delegate   part of the construction to its base. The compiler will synthesize a special   virtual property that returns the type  within which the objects can be equal.  The two objects are equal only if their types  are equal and their components are equal.  Did you know that? This is the only valid implementation of Equals.  The rest of the base class  will be straightforward.  One last complication will come  when converting objects to strings.  The compiler will synthesize a protected  virtual method that appends the object's   components at the back of a StringBuilder. There's a lot of fine logic here,   as you can witness. Have you ever developed this   kind of logic in your value objects? Well, compilers are not picky.  They do it all, so every record you declare  in C# will have all this logic implemented.  I didn't count the lines, but it looks like there  are some 50 of them in my custom implementation.  The variant based on records  consists of two lines of code.  I will remove my custom implementations  and not look back on them anymore.  If you wish to read the source code of  this demo, you can visit my Patreon page.  Join the growing community on Patreon and  get access to source code of this video   and all other videos on my channel. You can also join the Discord server   associated with this channel and participate in  discussions, ask questions about C# and .NET.  You're welcome. You can find the links in the description.  From now on it, will be  records all the way through.  It is not a good practice to  derive classes from one another.  A much better design is to define  an abstract empty record and then   derive sealed records for all the variants. Did I just complete a major refactoring in   just a few keystrokes? Oh, yes I did!  That is as close to functional discriminated  unions as you can do in C# today.  You can watch another video I have  made about discriminated unions in C#.  There's also the link in the description. Besides positional properties,   you can add a custom property of your choice. This property will obviously be optional when   constructing a record and it has a default value. You can use object initializer syntax to   set it during construction. You can change that property   in the with expression, but you  cannot access it in deconstruction.  It is allowed to redefine the positional property. That lets you change its visibility, make it   mutable, or add validation. Use the class parameter   to initialize the custom property. You can even override the printout if you must.  Not that I see much value  in this, but you can do it.  You can see that the printout  is now more condensed than it   would have been without my intervention. There are two more topics I want to cover   before ending this video. One is record structs.  It is usual to wrap a low-level  type, such as a GUID, or a string,   to give it a strong type and the domain meaning. This is the strongly-typed PersonId, the one you   would much favor in a DDD project. A record struct is a struct.  It has no object header, it supports no virtual  functions, it supports no inheritance, and it is   always copied by value. Keep them small.  The record structs are also  much faster than traditional   structs because they don't use reflection. Their equality members are are synthesized.  There's no faster way to compare  structs than what record structs do.  Therefore, don't use traditional structs anymore. Use record structs from now on.  And you can do some exciting  things with record structs.  What if I wanted to ensure that a string I  used is non-empty, non-whitespace, always?  First of all, properties in  record structs are mutable.  Remove the setter from positional  properties to make this record immutable.  Use validation to restrict the contained value. One last issue is that structs always have a   parameterless constructor that zeroes-out their  content, it sets all components to defaults.  So, you must make sure to reimplement the default  constructor if you wish to enforce validation.  A touch of genius - making a non-empty  string assignable to an ordinary string.  I like this. I can use this non-empty string definition in   all places I want to ensure that the proper string  has been assigned without further validation.  That is the design principle often  quoted in functional programming:   make invalid states unrepresentable. You cannot even try to construct a Book   object if you don't already have  a non-empty string as the title.  Since C# 10, you're allowed to  write record class, not just record.  Use this form if you find it more  intention-revealing, more readable, anything.  One last note and this demo is over. It is about the immutability of records.  C# only supports so-called shallow immutability. It will never modify a property on a record,   but if that property is referencing  a mutable class, a mutable object,   then it might happen that you mutate that object,  hence ruining the immutability of this record.  It is therefore your job to ensure that any  record you plan to make immutable only consists   of other immutable types and immutable records. I was very careful to do that precisely in my   design, so all the records I made  in this demo are deeply immutable.  This completes the deep dive on record  classes and record structs in C#.  Stay around, subscribe to my  channel, and watch other videos   I have made about object-oriented  and functional programming in C#.
Info
Channel: Zoran Horvat
Views: 15,833
Rating: undefined out of 5
Keywords: csharp, dotnet, programming, object-oriented programming, functional programming, c#, zoran, horvat, zoran horvat, c# programming, learn c#, c# tutorial, records, record types, c# records, c# record, dotnet record, .net record, value object, ddd, domain-driven design
Id: KFx9XHpoYV4
Channel Id: undefined
Length: 12min 55sec (775 seconds)
Published: Thu Jan 18 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.