C# 9.0 introduces a read-only object type created using the record keyword versus the keywords struct or class. This post introduces record instances created using positional object creation (recall C# Nominal versus Positional Object Create). A future post will introduce creating record instances defined using nominal syntax, demonstrate declaring record types with positional syntax, and show the use of the with keyword in conjunction with record types.
Up until C# 9.0 objects were defined using the class (reference type) or struct (value type) keywords. Such objects are typical mutable. It should also be recognized that anonymous types are reference types and tuples are value types, but objects are explicitly defined using the class and struct keywords.
The record keyword, to no surprise, defines an object of type record where record instances are immutable, reference object types. It is possible to define immutable types using class and struct such as the System.String class. The elegance of the record keyword is that it is clear that an instance of a record is immutable and the compiler creates constructs that support immutability that are built into each record instance. The following sections of this post expand on these ideas:
- Appendix A: Comparing Record Type Instances
- Appendix B: Record Type Compiler Synthesized Methods
The C# record to be implemented will be used to track patient hospitalization for a heath dashboard. That is certainly information that is important to provide but should also be immutable. The properties exposed by the Patients record are as follows:
- IsCovid19: true if patients are Covid-19 positive
- IsIcu: true if patients are in the I.C.U.
- IsVentilator: true if patients use ventilators.
- Count; number of patients
- Start: patient information start date
- End: patient information end date
- RangeType: type of patient information date range (day, week, month, quarter, biannual, year or custom)
The DataRangeType enumeration is used to specify the RangeType property of the Patients record:
The code for the Patients record is shown below. Some things to note with regards to the code:
- There is no set for each property (lines 18 to 30) as the properties can only be assigned at initialization. The init keyword could also be explicitly specified (see C# 9.0: Init Only Setters).
- Fit and Finish (discussed in C# 9.0: Fit and Finish) is used at lines 50, 60, 70, 90, 100, and 111.
- All source code below is provide in text form in Appendix C: Source Code.
Assigning a value to a Property After Instantiation
The code snippet below results in a complication error as it is a compile time error to modify a field or property of a record after it is instantiated:
The compiler errors from Visual Studio Code caused by line 116 (above) are as follows:
Appendix A: Comparing Record Type Instances
Microsoft's documentation on record types, Create Record Types, provides an excellent explanation of how record type instances are compared:
Appendix B: Record Type Compiler Synthesized Methods
Microsoft's overview of C# 9.0 features contains a comprehensive overview of record types (see What's new in C# 9.0: Record Types). To quote Microsoft's documentation on records:
Appendix C: Source Code
using System;
namespace HealthDashboard.Models
{
public enum DateRangeType
{
Day,
Week,
Month,
Quarter,
Biannual,
Year,
Custom
}
public record Patients
{
public bool IsCovid19 { get; }
public bool IsIcu { get; }
public bool IsVentilator { get; }
public int Count { get; }
public DateTime Start { get; }
public DateTime End { get; }
public DateRangeType RangeType { get; }
public Patients(
int count,
bool isCovid19,
bool isIcu,
bool isVentilator,
DateTime start,
DateTime end,
DateRangeType rangeType) =>
(Count, IsCovid19, IsIcu, IsVentilator,
Start, End, RangeType) =
(count, isCovid19, isIcu, isVentilator,
start.Date, end.Date, rangeType);
public static Patients CreateDay(
int count,
bool isCovid19,
bool isIcu,
bool isVentilator,
DateTime start)
{
return new(count, isCovid19, isIcu, isVentilator, start,
start.AddDays(1), DateRangeType.Day);
}
public static Patients CreateWeek(
int count,
bool isCovid19,
bool isIcu,
bool isVentilator,
DateTime start)
{
return new(count, isCovid19, isIcu, isVentilator, start,
start.AddDays(7), DateRangeType.Week);
}
public static Patients CreateMonth(
int count,
bool isCovid19,
bool isIcu,
bool isVentilator,
DateTime start)
{
return new(count, isCovid19, isIcu, isVentilator, start,
start.AddMonths(1), DateRangeType.Month);
}
public static Patients CreateQuarter(
int count,
bool isCovid19,
bool isIcu,
bool isVentilator,
DateTime start)
{
return new(count, isCovid19, isIcu, isVentilator, start,
start.AddMonths(3), DateRangeType.Quarter);
}
public static Patients CreateBiannual(
int count,
bool isCovid19,
bool isIcu,
bool isVentilator,
DateTime start)
{
return new(count, isCovid19, isIcu, isVentilator, start,
start.AddMonths(6), DateRangeType.Biannual);
}
public static Patients CreateYear(
int count,
bool isCovid19,
bool isIcu,
bool isVentilator,
DateTime start)
{
return new(count, isCovid19, isIcu, isVentilator, start,
start.AddYears(1), DateRangeType.Year);
}
public static Patients CreateCustom(
int count,
bool isCovid19,
bool isIcu,
bool isVentilator,
DateTime start,
DateTime end)
{
return new(count, isCovid19, isIcu, isVentilator, start,
end, DateRangeType.Custom);
}
}
}