ICYMI C# 9 New Features: Create Immutable Objects with Records
source link: http://dontcodetired.com/blog/post/ICYMI-C-9-New-Features-Create-Immutable-Objects-with-Records
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.
This is part of a series of articles on new features introduced in C# 9.
C# 9 introduced a new type of object that is neither a class or a struct. This new type is called a record.
In C# 9 a record is a reference type that has value type equality semantics (more on this below).
The main purpose of defining record types is to indicate immutability for a type that is “data-centric” or in other words does not have rich behaviour (such as data transfer objects, database records, etc).
How to Define a Record in C# 9
To define a record type you use the record keyword:
record Message1
{
public
int
Priority {
get
;
set
; }
public
string
MessageBody {
get
;
set
; }
}
We could now create an instance and then write it to the console window:
var
m1 =
new
Message1();
m1.Priority = 1;
m1.MessageBody =
"Hi"
;
Console.WriteLine(m1);
This would produce the following output:
Message1 { Priority = 1, MessageBody = Hi }
Notice in the preceding code that we are able to set Priority and MessageBody even after we have created the object – this is not immutable behaviour. To make a record immutable when declaring properties manually (see positional records below) you need to make the property setter init only:
record Message2
{
public
int
Priority {
get
; init; }
public
string
MessageBody {
get
; init; }
}
Now if you try and write the following code you’ll get a compiler error (“Init-only property or indexer … can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor”):
var
m2 =
new
Message2();
m2.Priority = 2;
m2.MessageBody =
"Hey there!"
;
To create Message2 instances you now need to set the properties when you create it, for example:
var
m2 =
new
Message2()
{
Priority = 2,
MessageBody =
"Hey there!"
};
What Are Positional Records in C#?
Positional records are a shorthand syntax for defining C# records. Behind the scenes they create init-only properties.
We could define a message class that is essentially the same as Message2 above with the following syntax:
record Message3(
int
Priority,
string
MessageBody);
Now we could create one with the following syntax:
var
m3 =
new
Message3(3,
"Good day sir!"
);
Or if you wanted to be explicit:
var
m3 =
new
Message3(Priority: 3, MessageBody:
"Good day sir!"
);
Even though behind the scenes we’re getting init-only properties, when you define a positional record you can’t use the following syntax:
var
m3 =
new
Message3()
// error missing arguments
{
Priority = 3,
MessageBody =
"Good day sir!"
};
You can think of positional records as a shorthand syntax that creates init only properties and a parameterized constructor automatically behind the scenes.
Equality
Records have value-like equality semantics:
Record instances in C# 9 by default are considered equal if they store the same values and are of the same record type:
var
m3a =
new
Message3(Priority: 3, MessageBody:
"Good day sir!"
);
var
m3b =
new
Message3(Priority: 3, MessageBody:
"Good day sir!"
);
var
m3c =
new
Message3(Priority: 3, MessageBody:
"BOO!"
);
Console.WriteLine($
"m3a == m3b : {m3a == m3b}"
);
// Outputs: TRUE
Console.WriteLine($
"m3b == m3c : {m3b == m3c}"
);
// Outputs: FALSE
If you tried to compare a Message3 object with a Message2 object you’ll get a compiler error.
If you want to, you can override things like Object.Equals in a record.
Note: C# 10 will be introducing record structs .
Immutability of Record Types
One thing to be aware of is that immutability of records types is “shallow” for properties that are reference types.
In other words while you can’t change the value of a value type property you can change the properties of reference type properties in a record:
var
m4 =
new
Message4(4,
new
[] {
"Dear sir"
,
"Good to see you."
,
"Good bye."
});
Console.WriteLine(m4.MessageLines[0]);
// OUTPUTS: Dear sir
m4.MessageLines[0] =
"Yo yo!"
;
// NO COMPILER ERROR
Console.WriteLine(m4.MessageLines[0]);
// OUTPUTS: Yo Yo!
m4.MessageLines =
new
[];
// ERROR MessageLines property object reference itself IS immutable
You can create a new immutable record object based on an existing immutable instance:
var
one =
new
Message3(Priority: 3, MessageBody:
"Good day sir!"
);
var
two = one;
Object two is a copy of one.
Note: record copies are “shallow” - any value type properties will have the value copied, but any reference type properties will only have the reference copied. That means that 2 record instances can have reference type properties that point to the same object. You change the object they point to and both records will be “updated” with (point to) the new value (because they share the reference to the same object in memory).
If a record is immutable, you can “update” it by creating a copy of it, and update some properties as required during the “copy” – you do this using the with keyword. For example to “update” the priority of an immutable record:
var
priority3Message =
new
Message3(Priority: 3, MessageBody:
"Good day sir!"
);
var
priority1Message = priority3Message with { Priority = 1 };
As before, if you create a copy and use with a shallow copy is still created.
Custom C# Record Output Formatting
When you declare a record, under the hood a PrintMembers method is generated. You can also provide your own:
record Message5(
int
Priority,
string
[] MessageLines)
{
protected
virtual
bool
PrintMembers(StringBuilder builder)
{
builder.Append($
"P:{Priority}"
);
for
(
int
i = 0; i < MessageLines.Length; i++)
{
builder.Append($
" {MessageLines[i]} "
);
}
return
true
;
}
}
Now the following code:
var
m5 =
new
Message5(5,
new
[] {
"Dear sir"
,
"Good to see you."
,
"Good bye."
});
Console.WriteLine(m5);
Would output:
Message5 { P:5 Dear sir Good to see you. Good bye. }
If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.
SHARE:
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK