DEV Community

Wilko van der Veen
Wilko van der Veen

Posted on • Updated on

For the record

As most C# developers know, since language version 9.0 on there is support for records.

The language is constantly changing, which is, kind of alright, I guess. The downside of this, is that I need to translate all 'the dialects' in my head.

The documentation for records in C# state the following:

When you declare a primary constructor on a record, the compiler generates public properties for the primary constructor parameters. The primary constructor parameters to a record are referred to as positional parameters. The compiler creates positional properties that mirror the primary constructor or positional parameters. The compiler doesn't synthesize properties for primary constructor parameters on types that don't have the record modifier.

For example:

public record Person(string FirstName, string LastName);
Enter fullscreen mode Exit fullscreen mode

translates to something like this:

public class Person : IEquatable<Person>
{
    public string FirstName {get; init;}
    public string LastName {get; init;}

    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as Person);
    }

    public bool Equals(Person other)
    {
        if (other == null)
            return false;

        return 
            (
                object.ReferenceEquals(this.FirstName, other.FirstName) ||
                this.FirstName != null &&
                this.FirstName.Equals(other.FirstName)
            ) &&
            (
                object.ReferenceEquals(this.LastName, other.LastName) ||
                this.LastName != null &&
                this.LastName.Equals(other.LastName)
            );
    }
}
Enter fullscreen mode Exit fullscreen mode

So in fact the records are still classes, but the syntax is different. So in my head I need to translate the structure of a record to the 'old school' classes
So I need to know more than when I read the 'generated' code, which I can understand immediately.

It is like we all speak English but all different dialects, which in my case costs me extra effort to understand what someone is saying when I first need to translate, for example Welsh to English before I understand what someone is saying.

It seems that Microsoft (or the community) is moving more and more to having to know a lot instead of having to read the code to understand what is happening, because the level of abstraction is getting higher.

Using records, on the other hand, the value equality methods you do not have to implement yourself, because the compiler generates these methods for you. But again, you need to know what methods are being generated to understand how value comparison for records work, but doing value comparison is hard enough by itself.

To solve the above issue, source generators could be used to translate records to classes.

What do you think, are records a good trade-off for writing less code and needing to know a bit more about how for example comparison of property values are done for records?

Top comments (3)

Collapse
 
peledzohar profile image
Zohar Peled

Some nitpicking:

  1. First, conceptually speaking, records are somewhat of a mix between value types and reference types - So while it is true that they are lowered to classes in compilation, there are semantic considerations to help you choose between a record and a class.
    Because of that, records also comes with a compiler-generated overloads of the == and != operators and Equals() implementations (among other things) , making all comparisons between records based on the values of their properties, not on references (as apposed to classes).

  2. Positional properties in records are translates into get/init properties, not get only properties.

All in all, the choice between using a record an a class is a semantic choice, it's not about writing less code - but about choosing the correct tool to work with.

Collapse
 
wilkovanderveen profile image
Wilko van der Veen

Thanks for the additions @peledzohar. If the compiler creates get/init properties, then I asume there will be no constructor generated for the respective constructor parameters? It that the case?

Collapse
 
peledzohar profile image
Zohar Peled • Edited

No, positional parameters will generate a constructor with matching arguments.
This is well documented:

When you use the positional syntax for property definition, the compiler creates:

  • A public autoimplemented property for each positional parameter provided in the record declaration.
    • For record types and readonly record struct types: An init-only property.
    • For record struct types: A read-write property.
  • A primary constructor whose parameters match the positional parameters on the record declaration.
  • For record struct types, a parameterless constructor that sets each field to its default value.
  • A Deconstruct method with an out parameter for each positional parameter provided in the record declaration. The method deconstructs properties defined by using positional syntax; it ignores properties that are defined by using standard property syntax.