Avoid C# 9 Record Gotchas (2023)

C# is quickly evolving, and it may be overwhelming to keep up with every new feature. C# 9 has been out for over a month now, and I thought it would be a good idea to consolidate my thoughts on what I consider to be the most exciting feature: Record types. I don’t consider myself an expert by any means, and I doubt anyone outside of Microsoft has had enough experience to genuinely know the ins and outs of the record type. That said, in this post, we’ll explore “gotchas” that may confuse folks as they make the transition from class to record.

Here are the elements of using a record type that may be the source of bugs and hours of frustrating debugging in no particular order. Keep these in mind when considering using records in your codebase.

What Is A Record?

Don’t know what the record type is? Don’t worry. It’s only been a month since the release of .NET 5, and you’re likely not alone. The record type is a new C# language type that allows developers to create immutable objects with additional value-based equality methods.

C# 9.0 introduces record types, a reference type that provides synthesized methods to provide value semantics for equality. Records are immutable by default. –Microsoft

Immutability and lack of side-effects can be advantageous for folks working in multi-threaded applications or adopting a more functional approach to C# development. Passing data by value ensures that there are fewer opportunities for resource contention and deadlocks. Time will tell if record types deliver on that promise.

The most crucial keyword when dealing with record types is unsurprisingly the record keyword. We can convert most class types to a record by switching the class keyword to record.

public class Pet { public string Name {get;set;}}// change topublic record Pet { public string Name {get;set;}}

To get the most of the record types abilities, we may want to consider changing all properties to use the init keyword. By applying the keyword, we enforce compiler directives only to set the value once during object initialization.

public record Pet { public string Name {get;init;}}

We can then use the with keyword to create a duplicate copy of our instance.

var samson = new Pet { Name = "Samson" };var guinness = samson with { Name = "Guinness" };
(Video) Intro to Records in C# 9 - How To Use Records And When To Use Them

Great! Now that we’ve had a quick crash course on record types let’s get to some issues folks may run into when using them.

Positional Parameter Syntax

One of the most significant advantages to the record type is a shorthand syntax for declarations.

public record Person(string First, string Last);

The record type is a definition, and the compiler synthesizes many of those features at compile time. The syntax will produce two string properties for First and Last on our Person record type. What folks may not realize is that the First and Last declared in our code are constructor parameters, also known as positional parameters. Why is it important to make that distinction? Well, let’s look at some code that developers may expect to work but won’t.

public record Person( [Description("First Name")] string First, [Description("Last Name")] string Last);

We place a Description attribute on each parameter, and some folks might expect that the compiler will transfer our Description attributes to the properties, but they are not. The distinction is critical for developers using metaprogramming to decorate additional data onto their types. Developers utilizing reflection will need to account for shorthand syntax and new locations that developers may place attributes. For folks using frameworks like ASP.NET, these distinctions are already handled and should work with DataAnnotation attributes.

There is a workaround to this issue. We can place attributes on properties using the property: prefix, which tells the compiler to place these attributes on our generated properties.

public record Person( [property:Description("First Name")] string First, [property:Description("Last Name")] string Last);

This technique “works”, but is dependent on both developers knowing it exists as an option, and library authors looking at attributes parameters and properties on a record. To say the least, this will likely cause several issues for years to come in the .NET community.

(Video) 5 New Features in C# 9 (not including Records)

Inheritance

Record types can inherit from each other, but they may not inherit from a class. Record hierarchies and class hierarchies must remain separate and cannot share a lineage. The limitation will lead many folks to choose an all-or-nothing approach when adopting record into their applications. While not immediately problematic, we will see where this approach could reveal more potential issues down the line.

Deconstructing Positional Parameters Of Two or More

Deconstruction is one of those synthesized features we get for free with record types. The ability to breakdown a record into its simplest parts can help reduce noise in our code and allow us to pass those deconstructed explicit values rather than entire records. One significant limitation for record deconstruction is that it only works when the record type definition has two or more positional parameters. This is a limitation in the C# language, not an omission of the synthesized deconstruct method.

In the following example, we get a synthesized deconstructor, but we cannot call it using syntactic enhancements because we only have one positional parameter.

// one positional parameterpublic record Person(string Name);var person = new Person("Khalid");// not going to workvar (name) = person;// this works// but ewwwww....pet.Deconstruct(out var whatevs);

By adding a new positional parameter of Last, we can now invoke a deconstructor that matches our type’s parameter order. The , is an essential syntax when deconstructing types into their parts.

public record Person(string Name, string Last);var person = new Person("Khalid", "Abuhakmeh");// works because of the `,` between the parenthesisvar (first, last) = person;

I’ll admit, this one is an extreme edge case since most record definitions are likely to use more than one positional parameter. We also need to note that property definitions are not part of the deconstructors synthesized for our types.

public record Person(string Name, string Last) { public string Number { get; init; }}

Looking at the IL of our Person record shows that only the First and Last properties are part of the deconstructor.

.method public hidebysig instance void Deconstruct( [out] string& First, [out] string& Last ) cil managed{ .maxstack 8 IL_0000: ldarg.1 // First IL_0001: ldarg.0 // this IL_0002: call instance string Person::get_First() IL_0007: stind.ref IL_0008: ldarg.2 // Last IL_0009: ldarg.0 // this IL_000a: call instance string Person::get_Last() IL_000f: stind.ref IL_0010: ret} // end of method Person::Deconstruct
(Video) What are record types in C# and how they ACTUALLY work

Now is a great time to talk about deconstructor behavior and inheritance together.

Deconstruction Depends On The Handle Type Deconstructor

The deconstructor called will depend on the type handle to the instance we refer to in our current context, not the instance’s original record type. Let’s take a look at these two record types.

public record Person(string First, string Last);public record Other(string Last, string First) : Person(First, Last);

The Other type inherits from the Person type, with the positional parameters reversed. Let’s look at some code that shows where folks could get some unexpected output.

var other = new Other("Abuhakmeh", "Khalid");string first = null;string last = null;(first, last) = (Person)other;Console.WriteLine($"{first} {last}");// Not Person, but Other(first, last) = other;Console.WriteLine($"{first} {last}");

The deconstructor for Person will return First followed by Last, whereas the deconstructor for Other will perform the inverse, returning Last, then First.

Khalid AbuhakmehAbuhakmeh Khalid

Deconstructor behavior may or may not be what we expect. Developers coming from an Object-oriented programming background may expect polymorphism to be the critical factor here. In contrast, folks invoking interface behavior may expect this to be the result they were expecting.

Different Types Can’t Be Equal

Folks who use data transfer objects or “plain old c# objects” may be familiar with adding properties of Id. While the record type comes with many value-based operations, there are extreme caveats. The biggest issue might be that equality is value-based and includes a check that the types match. Two records of different types are not equal, even when they share identical property values. The distinction includes types that inherit from the same base class. In the example above, with Other and Person, they can never be equal using the synthesized operators.

(Video) #177: C# 9 Record Types Revisited

Person person = new Person("Khalid", "Abuhakmeh");Other other = new Other("Abuhakmeh", "Khalid");// not equal to each other// even though values matchConsole.WriteLine(person == other);public record Person(string First, string Last);public record Other(string Last, string First) : Person(First, Last);

As we would expect, the result of the following code is False.

Reflection Bypasses Init Setters

We talked about the advantage of immutability with the record type. Well, it’s mostly an advantage during development time, but we can alter record instances the same way we can any object instance during runtime.

using System;using System.Linq;Person person = new Person("Khalid", "Abuhakmeh") { Number = 1 };var propertyInfo = typeof(Person).GetProperties() .Where(p => p.Name == nameof(person.Number)) .First();propertyInfo.SetValue(person, 3);Console.WriteLine(person.Number);public record Person(string First, string Last){ public int Number { get; init; }};

Here, we can modify the value of what should be an immutable Number property. The mutability of values is an important consideration when working in codebases that rely heavily on reflection.

Generic Constraints Mismatch

Since records are relatively new, they share some of the same DNA as the class type. The C# language has not adapted generic constraints to support only passing a record type, but the record type does satisfy the class constraint.

using System;using System.Linq;Person person = new Person("Khalid", "Abuhakmeh") { Number = 1 };Hello.Greet(person);public record Person(string First, string Last){ public int Number { get; init; }};public static class Hello{ public static void Greet<T>(T value) where T : class { Console.WriteLine(value); }}

I could see the need to constrain parameters based on their record interface, thus ensuring synthesized methods are available and any comparisons will be are based on value rather than reference. Generics are crucial for open-source projects, and they may want to adopt the record type cautiously. Additionally, it may lead to strange behaviors as users begin to pass in record instances rather than class instances.

Conclusion

Record types will open up many new opportunities for us as developers and generally will make our code bases smaller and less prone to errors during development. The drastic change in syntax will likely have folks assuming behavior and introducing bugs early on into their codebase as they transition from previous C# syntax to C# 9. Not only that, but OSS maintainers who relied on generic constraints may be getting a trojan horse of unexpected behaviors. Records are an excellent addition to the language, but new bright and shiny features can distract from the sharp edges ready to hurt us.

Can you think of any other edge cases that folks should consider when looking at record types? Please let me know in the comments, and please share this post with friends.

(Video) C# 9 Language Features

References

FAQs

What is the difference between record and class performance in C# 9? ›

The main difference between class and record type in C# is that a record has the main purpose of storing data, while class define responsibility. Records are immutable, while classes are not.

Should I use records in C#? ›

Use a record when an object's only purpose is to contain public data. On other hand, use a class if your object has unique logic. Classes are mutable so even if they have the same data, doesn't mean they are the same. For example, if we think about a class that represents a window.

What is record type C# 9? ›

C# 9 introduces records, a new reference type that you can create instead of classes or structs. C# 10 adds record structs so that you can define records as value types. Records are distinct from classes in that record types use value-based equality.

Are records immutable C#? ›

A record struct declares a value type. Positional properties are immutable in a record class and a readonly record struct . They're mutable in a record struct .

Are recorded lectures better? ›

Videos led to a greater gain in confidence and perceived ability. However, students still preferred live lectures to videos. Conclusions: This study showed greater performance scores and confidence when using video podcasts, with junior residents improving more with podcasts.

Is C# more performant than Python? ›

Additionally, due to its much simpler syntax, Python requires far fewer lines of code than C# does to execute the same task, theoretically making the process faster. And yet, C# proves to be quite the competition. In practice, C# programs actually run faster than Python ones, and they use up less memory to do it.

Why is C# outdated? ›

C# is a programming language that was released in 2002 and is implemented in different of applications, including web development, desktop applications, and all phases of scripting languages. So it's not extremely old; compare it to PHP, Java, JavaScript and Python, which are all considerably older languages.

Why do people still use C#? ›

C# is often used to develop professional, dynamic websites on the . NET platform, or open-source software. So, even if you're not a fan of the Microsoft architecture, you can still use C# to create a fully-functional website.

Do records degrade with use? ›

The more you use your records, the more they will deteriorate. Like anything physical, the friction between two surfaces will gradually add wear and tear (in this case the contact of the stylus on the record's grooves). On top of this, you'll also have the handling of the records and how often you move them around.

Does .NET core 3.1 support C# 9? ›

C# 9 is supported only on .NET 5 and newer versions.

How do I know which record type is default? ›

  1. From your personal settings, enter Record Type in the Quick Find box, then select Set Default Record Types or Record Type Selection—whichever one appears. ...
  2. Select the data type to specify that you want to use the default record type whenever you create that type of record. ...
  3. Click Save.

What is the most common record type in? ›

Firstly, address (A) records are the most common record type by far. In brief, A records map domain names to IPv4 addresses. Secondly, as the internet gradually makes the transition to IPv6, there are AAAA records (spoken as “quad A”).

Are records serializable? ›

Because a record is an immutable data carrier, a record can only ever have one state, which is the value of its components. Therefore, there is no need to allow customization of the serialized form.

Can records have constructors C#? ›

You can add additional members (e.g. properties, methods) to records. If necessary, you can add additional constructors. Records can inherit from other records.

Can records inherit from classes C#? ›

You can inherit your records from other records or from object . You can not inherit a record from any other class than object , and you can not inherit a class from a record.

What are the disadvantages of recorded lectures? ›

“Recording lectures often encourages poor listening. When you know everything is being recorded, you might tend to daydream or not pay full attention to all the information being presented” (Pros and Cons of Recording Lectures, n.d.).

Is recording online lectures illegal? ›

Is it illegal to record class lectures? No! It is perfectly fine to record courses if it has been accepted as accommodation for the disability of the student to offer significant access to the educational experience. However, there are certain rules that you should take into consideration while recording the courses.

Is recording classroom lectures illegal? ›

Be aware that, under California law, it is illegal to make audio recordings of others without their knowledge, though this does not apply where there is “no expectation of privacy,” in locations like a lecture hall at a public university such as UCSF.

What is a class performance? ›

School performance or just performance is used as a measure for students' academic achievement in terms of school grades.

What are the benefits of record type? ›

“Record types let you offer different business processes, picklist values, and page layouts to different users. You might create record types to differentiate your regular sales deals from your professional services engagements, offering different picklist values for each.

What is a recorded performance? ›

Recorded Performance is a tool that records the performance data of SAN resources. The data that is collected depends on the counters and instances configured for the recording session.

How are records different from field class 8? ›

A record: Contains specific data, like information about a particular employee or a product. A field: Contains data about one aspect of the table subject, such as first name or e-mail address.

Videos

1. Big Changes in .NET 5, C# 9, and Visual Studio 2019 (v16.8)
(IAmTimCorey)
2. Every feature added in C# 10 with examples
(Nick Chapsas)
3. Coding Shorts: Top-Level Statements in C# 9
(swildermuth)
4. 8 await async mistakes that you SHOULD avoid in .NET
(Nick Chapsas)
5. What's New in C# 9 - Records & Init Only Setters
(modulardev)
6. How to bend reality to your will with C# Source Generators
(Nick Chapsas)
Top Articles
Latest Posts
Article information

Author: Annamae Dooley

Last Updated: 02/18/2023

Views: 5808

Rating: 4.4 / 5 (45 voted)

Reviews: 92% of readers found this page helpful

Author information

Name: Annamae Dooley

Birthday: 2001-07-26

Address: 9687 Tambra Meadow, Bradleyhaven, TN 53219

Phone: +9316045904039

Job: Future Coordinator

Hobby: Archery, Couponing, Poi, Kite flying, Knitting, Rappelling, Baseball

Introduction: My name is Annamae Dooley, I am a witty, quaint, lovely, clever, rich, sparkling, powerful person who loves writing and wants to share my knowledge and understanding with you.