TL; DR
The main reason to adopt this mechanism is to have a simple data structure that does not adopt the common style in object orientation, that is, there is a preference for functional style.
The record
used in the way he was thinking is a type with semantics of value but that is stored as reference and has time of life detached from those who created it. That’s why he’s a substitute for a struct
and not one exactly of the class.
Speaking as mechanism, record
, for all intents and purposes, it is a class, so I believe there is this confusion. There is a small change and with that a different motivation. This mechanism has no limitations in relation to what the class can do, it has, optionally, something else. The concept is quite different.
Myth of immutability
In the end I quote the official documentation as a source to show what the mechanism is and how it works. But in the description of what is new in language she is clearly wrong at least one point, which undermines the credibility of the rest.
C# 9.0 introduces record types, which are a Reference type that provides synthesized methods to provide value Semantics for Equality. Records are immutable by default.
Source.
Records are not immutable by default, at least not different from how class is. A field is mutable by default in a record
, if you don’t want him to change, you have to say that he is readonly
. All examples use properties, and here we can even consider that they are immutable by default because only if you put a method set
in it is that it happens to be changeable indeed. Ok, but this is equal to the class.
It is a fact that record
was created to facilitate the use of immutable structures, but it does not need to be so. Nothing different has in it that requires being immutable. And class can do exactly the same.
Now there is a new way to manipulate the property that is the init
. But this is not a resource of record
, is something new that works with the class as well. This is a mechanism that encourages immutability, at least public, since it lets put an initial value, but no longer mess with the value after that.
Nothing prevents the field from being changed internally differently. To be totally unchangeable you still have to make the field be readonly
. And the init
works well with this, in class or in the register.
There is a situation that record
is immutable by default:
public record Pessoa(string Nome, int Idade);
In the very simple form of syntax record
this behavior was adopted, so it can only touch the value during the initialization of the object, and as we see there are no other parts that move internally. In practice it creates a property for you with the init
.
In this form is not that immutability is the standard, it is the only possible way, at least in the members described in this syntax called Primary constructor, nothing prevents having other fields or properties and they are changeable, which will make the structure as a changeable whole.
Note that immutability is shallow, that is, only in the basic object of each property, the objects that make up this object do not become immutable automatically, it is their problem to take care of it if it is the case. It has not changed in relation to what already happened with the class.
Builder
To be clear, support for immutable objects already occurred with classes. There has been a simplification of the way to do this and one of those simplifications is to no longer need to create a constructor to do the initialization, but this is a Feature of init
and not of record
.
I’m afraid people start ignoring constructor, which may be ignored in some cases when you have the init
, but not always should. See more in What good is a builder?.
When what you want is the object to be immutable, the record
may be a preference, but it’s not just that and this issue is collateral.
Real reason to use a record
Simplification is a slightly stronger reason, but still not the strongest of all to use the record
.
Guys with value semantics tend to be immutable, it is true, but it is not obligatory that they are.
When you wanted semantics by value you could use classes, the biggest example is string
. Then the record
gave nothing new to the language.
Types with semantics by value and mainly immutable could already be adopted with struct
and more clearly and guaranteed with readonly struct
.
The record
It should be compared to a simple structure, because that’s what it is. You shouldn’t compare it to a class. When you need a class, use a class.
The biggest reason to adopt a struct
, and the same goes for the record
is to have a simple data structure. These types composition forms have as their center the use of dice and not of a object complex.
Read In programming, what is an object?. And forget that a little bit. Here I am using the term object by the definition of object orientation. An object is a complex component where you have something cohesive and you access behaviors as if it were a single thing. Data needed for all this to work is not important to the programmer, it’s all implementation detail, the object is what matters. For all this to function mutability is important and it is customary to have several methods that manipulate this object. Behaviors tend to be very different between objects.
In a struct
the focus is the die, so it is often said that the use of primitive types or simple types defined by the user (programmer) is a violation of object orientation. How nice.
One struct
is a simple datum, not a complex composition. The datum is what interests you, not a cohesive behavior on top of something complex. It always has semantics by value and in fact it is usually by value, except if you use a ref struct
.
A record
serves for the same thing, to be a structure focused on the data, with semantics by value, probably in an immutable way. No wonder initially he would call himself data class
, that is, it would be a data class, and not a class of an object, which is the normal.
So why use one record
in place of a struct
if both serve the same purpose?
When you need all this already described but need unlink life time with the creator of this data object, then the record
is recommended. A record
is always a type by reference, although the default semantics is by value, and it is always stored in the heap, unless it changes one day. Being in the heap life time is controlled by having a reference to it, but no matter where it comes from. It does not depend on the method that created this object to be running, or on an object that initially adopted it in a field of still existing, it only depends on some reference to it to exist.
So the mechanism is a class. In fact everything you can do with a class can do with it. And everything you do with it can be done with a class, always could.
Okay, I get why you adopted him, but then why did you invent it if you could do it before?
To facilitate use with value semantics. And also to make it clearer that you have a data object and not an object in the direction of object orientation. For some time I’ve been saying that C# wants to move away from the idea of OOP (without abandoning). This form encourages a more functional use of language (without adopting altogether).
And how it makes it easier?
It automatically provides methods that are usually needed on an object with semantics by value, which is what happens when you create a struct
, but it doesn’t happen when you create a class. Even most of the classes that people create are wrong because they are incomplete, even if they work because you never end up using what is not done, or "tidy up" the situation to get around the lack of the mechanism done correctly. So always put:
Then understand the record
as a ref struct
which is in the heap and is managed by Garbage Collector. If you don’t know, the ref struct
can only be in the stack, it cannot be part of an object in the heap (to struct
can).
That’s the decision you have to make.
So when I think about creating a class I don’t even have to think about whether I should be a record
?
Not exactly. The problem is that people are addicted to creating classes when, in some cases, they should actually create struct
, or ref struct
. And I even had a reason to adopt a class when it’s right to create a form of struct
. Without a mechanism that allows life time to be disconnected with the person who created the object, especially ref struct
only by being able to use within methods, the only solution that existed was the class. But it was wrong, although it worked well. Now it has the proper mechanism. That is the real reason to exist and adopt a record
.
Although it is possible it is inappropriate to use reference semantics in a record
of the C#.
More information on the record
Wikipedia gives a very reasonable definition of what a record. Universally a record does not differ from what it already knows, it is only a Plain Old Data with value semantics. There are no more details about how they should be implemented.
To get an idea, what we call struct
in C# is called record
in Pascal/Delphi. Some people call struct
of record. Conceptually don’t compare it to a class, although it is one in C#, but it is implementation detail.
The idea of being a reference is something that served the implementation of C# well since the language already had a form of record totally by value. But semantics by value is maintained to still be a record
.
The C# compiler provides suitable implementations of some methods that all objects have, among them the equality method and comparison operators, the GetHashCode()
, ToString()
and forms copy and clone the object. In some circumstances may provide a Deconstruct()
, the getters and initializers or other methods (such as PrintMembers()
and the implementation can evolve and have others). The constructor is also created allowing the initialization of the data. So he does even a little bit more than the compiler does for a [ref
] struct
. And he knows how to do it the right way, the program doesn’t always know. This is an important difference for the class.
The record
does not cease to be a class, so it has inheritance, for example, and that would be a good reason to adopt it when you need this language resource that is not available for a [ref
] struct
. Just be careful because inheritance in types with semantics by value can be a little complicated in some cases.
Important to say that these compiler-created methods can be replaced by a version of you if it suits you better and knows what you’re doing. And it can have other additional methods, like a class, just seems a little abuse. The record
exists mainly to meet demands of anemic domain.
I still don’t have fully formed opinion about using a record
as just an immutable class, to facilitate competition for example, because the class serves that as well. But I think people will use it because it’s easier than class and because they can’t create the right methods in class. Many people will believe that it is only an immutable class because it already has a lot of second-class material spread over the internet saying this, first-class people don’t make that mistake.
Completion.
Use record
s to:
- Dtos and anemic domains
- Simple guys, like a
struct
, that need to have indefinite life time
- Types focusing on data (not behavior), especially when there is a relationship between them but it is not a single object, in a way as if it were a named tuple
- Immutable classes that will be used in competitive contexts and you can handle it.
One problem I see is that there is incentive in the examples to adopt classes that are clearly changeable as immutable. This is the case with person which, by definition, changes its state throughout life, in addition to having various behaviors in most scenarios, and so a class has always been very suitable.
In a situation that needs to remain unchanged to facilitate competition a record
can be adopted, but when you need to change one data on that object, another is created, but it’s the same person, you have to deal with it. It’s the same problem of microservices, you get inconsistencies if you don’t synchronize objects that are different with altered states but represent the same thing, and it’s a more difficult problem to solve. Note that not always this is complicated, then it is ok to use.
Immutable objects tend to be less efficient in various situations where they would have to make a mutation requiring copying, but may be better in others where the domain really is immutable. Do not force something to be immutable if it is not or will not be used in an environment that needs to be concurrent.
Use where it helps you and does not create complication.
See the documentation. And note that F# already had a similar mechanism because it has always favored functional style over object-oriented. And see a good use tutorial.