Fundamentals of Records

Introduction

In previous lessons, we were introduced to classes as a way to create a layout for objects. We saw other options to create objects, such as tuples and structures. Like those options, a record is a technique to combine characteristics that describe an object. There are many similarities between classes, structures, and records. We are going to review many of those issues before getting to the differences between classes and structures on one hand, and records on the other hand.

Practical LearningPractical Learning: Introducing Records

  1. Start Microsoft Visual Studio. Create a Console App (that supports .NET 7.0 (Standard Term Support)) named TaxPreparation5
  2. To start a new file, on the main menu, click Project and click Add New Item...
  3. In the left list of the Add New Item dialog box, click Code and, in the middle list, click Code File
  4. Change the file Name to IncomeTax
  5. Click Add
  6. In the empty document, type the following lines:
    namespace TaxPreparation5
    {
    
    }

Creating a Record Layout

To create a record, you use a keyword named record. The primary formula to follow is:

access-level record name {}

As always when you create a layout for an action or an object, you can start with some options that sometimes are not required. The primary option you can apply is an access level. In many cases, an access is not required but if you decide to use one, apply the public or internal to your record. The option(s) is (are) followed by the record keyword. As always, you must name the layout you are creating. The name of a record follows the rules of names of classes, structures, enumerations, and namespaces. As seen with those objects, a record must have a body, which starts with an opening curly bracket { and ends with a closing curly bracket }.

Practical LearningPractical Learning: Creating a Record

Creating a Record Object

When you create a record, you are simply providing a skeleton for an eventual object. To get an actual object, declare a variable of the record. You can use the name of the record as type. This is followed by a name. The name of the object follows the rules of names of variables. Add the assignment operator. Initialize the object with the new operator. Add the name of the record and parentheses.

Practical LearningPractical Learning: Accessing a Record

  1. In the Solution Explorer, right-click Program and click Rename
  2. Type TaxPreparation (to get TaxPreparation.cs) and press Enter
  3. Change the TaxPreparation.cs document as follows:
    using static System.Console;
    using TaxPreparation5;
    
    IncomeTax it = new IncomeTax();
  4. To execute, on the main menu, click Debug and click Start Without Debugging
  5. Close the window and return to your programming environment

A Field in a Record

When creating a record, you should fill it with some useful members. As seen with classes and structures, the simplest thing you can do in a record is to declare a variable of a primitive type. Such a variable is also called a field. Here is an example:

internal record Employee
{
    string nbr;
}

You can then use that field. Before doing that, you should apply an appropriate access level. As a reminder, if you apply the private level, the field can be accessed only by members of the same record. If you want the field to be accessed outside the record, you should apply the public or the internal levels.

The Properties of a Record

A property is a characteristic that allows a record to communicate or exchange values with other objects. As seen with classes and structures, all options to create properties are available in a record. As a reminder, if had added a private field to a record, you can create a complete property for it, with a get and a set clauses. Here is an example:

internal record Employee
{
    int nbr;

    public int EmployeeNumber
    {
        get
        {
            return nbr;
        }

        set
        {
            nbr = value;
        }
    }
}

After creating a property, you can access it the same way you would access a property of a class or structure. Here is an example:

Employee staff = new Employee();

staff.EmployeeNumber = 428_350;

Console.WriteLine("Employee #: {0}", staff.EmployeeNumber);

internal record Employee
{
    int nbr;

    public int EmployeeNumber
    {
        get
        {
            return nbr;
        }

        set
        {
            nbr = value;
        }
    }
}

This would produce:

Employee #: 428350

Press any key to close this window . . .

In the same way, you can use a conditional statement in a clause to accept a value, change a value, or reject a value. Here is an example:

Employee staff = new Employee();

staff.EmployeeNumber = 428_350;
staff.HourlySalary = 8.95;

Console.WriteLine("Employee #:    {0}", staff.EmployeeNumber);
Console.WriteLine("Hourly Salary: {0}", staff.HourlySalary);

internal record Employee
{
    int nbr;
    double sal;

    public int EmployeeNumber
    {
        get { return nbr; }
        set { nbr = value; }
    }

    internal double HourlySalary
    {
        get
        {
            if (sal <= 10.00)
                return 15.55;
            else
                return sal;
        }

        set
        {
            sal = value;
        }
    }
}

This would produce:

Employee #:    428350
Hourly Salary: 15.55

Press any key to close this window . . .

In the same way, you can create an automatic property in a record. Here are examples:

internal record Tractor
{
    public string? ModelName    { get; set; }
    public float   EnginePower  { get; set; }
    public int     LiftCapacity { get; set; }
    public int     MowerHeight  { get; set; }
    public decimal Price        { get; set; }
}

You can create an automatic property in a record

An Enumerated Property

As seen with structures and classes, a field of a record can be an enumerated type. Here is an example:

public enum FilingStatus { Single, Married, Unknown }

internal record Employee
{
    FilingStatus category;
}

In the same way, you can create a property of an enumeration type. Here is an example:

public enum FilingStatus { Single, Married, Unknown }

internal record Employee
{
    private FilingStatus category;

    public FilingStatus FilingStatus
    {
	get
	{
	    return category;
	}

	set
	{
	    category = value;
	}
    }
}

Of course, you can create the property as an automatic one.

The Methods of a Record

Like a class or a structure, a record can have methods. You create the method exactly the same way you would create a method in a class or structure. Here is an example:

public enum FilingStatus { Single, Married, Unknown }

internal record Employee
{
    private FilingStatus category;

    public FilingStatus FilingStatus
    {
        get
        {
            return category;
        }

        set
        {
            category = value;
        }
    }

    public string? FirstName { get; set; }
    public string? LastName  { get; set; }

    public string GetFullName()
    {
        if (LastName == "")
            throw new System.Exception("The last name is required for each employee.");

        return FirstName + " " + LastName;
    }
}

Everything else we reviewed for methods is available for records. For example, you can overload a method by passing different arguments to methods that use the same name. Here are examples:

public enum WorkCategory { Employee, Manager, Contractor, Seasonal, Unknown }

internal record StoreItem
{
    public string? PasswordManagement { get; set; }
    public string? HiringSupervision  { get; set; }
    public string? Description        { get; set; }

    // A method with a Boolean parameter
    public void Validate(bool isvalid)
    {
        if(isvalid)
            PasswordManagement = "Can Change Account Password";
        else
            PasswordManagement = "Regular Employee";
    }

    // A method with an enumerated parameter
    public void Validate(WorkCategory level)
    {
        if (level == WorkCategory.Manager)
            HiringSupervision = "Can Approve Hiring";
        else
            HiringSupervision = "Regular Employee";
    }

    // A method with two parameters
    public void Validate(int type, WorkCategory level)
    {
        switch (type)
        {
            case 2 when level == WorkCategory.Contractor:
                Description = "The job is a contract position on a temporary basis.";
                break;
            case 1 when level == WorkCategory.Seasonal:
                Description = "This is a full-time seasonal employee hired when season is at high pick.";
                break;
            case 3 when level == WorkCategory.Employee:
                Description = "The employment is be determined.";
                break;
            case 1 when level == WorkCategory.Manager:
                Description = "This is a managerial position with corporate credit cards.";
                break;
            case 2 when level == WorkCategory.Employee:
                Description = "This is a part-time employment position but put a regular payroll.";
                break;
        }
    }
}

Records and Functions

Returning a Record

Once you have created a record, it becomes a regular type. You can involve it with a function. For example, you can create a function that returns a record. To start, create a function or method whose return type is an existing record. In the body of the function, process the values any way you like. Before the closing curly bracket of the function, you must return an object that holds a record object. Here is an example:

using static System.Console;

public class TaxPreparation
{
    Employee Create()
    {
        WriteLine("Enter the information about the employee");
        Write("Employee #:  ");
        int number = int.Parse(ReadLine()!);
        Write("Frist Name:  ");
        string fName = ReadLine()!;
        Write("Last Name:   ");
        string lName = ReadLine()!;
        Write("Hourly Rate: ");
        double salary = double.Parse(ReadLine()!);

        return new Employee()
        {
            EmployeeNumber = number,
            FirstName = fName,
            LastName = lName,
            HourlySalary = salary
        };
    }
}

internal record Employee
{
    public int     EmployeeNumber { get; set; }
    public string? FirstName      { get; set; }
    public string? LastName       { get; set; }
    public double  HourlySalary   { get; set; }
}

An Argumented Record

Since a record is a type, you can pass it as argument. To do that, when creating the function, specify the type of a parameter as a record type. In the body of the function, use or ignore the parameter. When calling the function, make sure you pass an argument that holds a record type. Here is an example:

using static System.Console;

Employee staff = Create();

Show(staff);

Employee Create()
{
    WriteLine("Enter the information about the employee");
    Write("Employee #:  ");
    int number = int.Parse(ReadLine()!);
    Write("Frist Name:  ");
    string fName = ReadLine()!;
    Write("Last Name:   ");
    string lName = ReadLine()!;
    Write("Hourly Rate: ");
    double salary = double.Parse(ReadLine()!);

    return new Employee()
    {
        EmployeeNumber = number,
        FirstName = fName,
        LastName = lName,
        HourlySalary = salary
    };
}

void Show(Employee staff)
{
    WriteLine("=========================================");
    WriteLine("  - Human Resources -");
    WriteLine("-----------------------------------------");
    WriteLine("-=- Employee Detials -=-");
    WriteLine("=========================================");
    WriteLine($"Employee #:   {staff.EmployeeNumber}");
    WriteLine($"First Name:   {staff.FirstName}");
    WriteLine($"Last Name:    {staff.LastName}");
    WriteLine($"Gross Salary: {staff.FirstName}");
    WriteLine($"Last Name:    {staff.HourlySalary:f}");
    WriteLine("=========================================");
}

internal record Employee
{
    public int     EmployeeNumber { get; set; }
    public string? FirstName      { get; set; }
    public string? LastName       { get; set; }
    public double  HourlySalary   { get; set; }
}

Here is an example of running the program:

Enter the information about the employee
Employee #:  913740
Frist Name:  Jennifer
Last Name:   Beals
Hourly Rate: 31.25
============================================
 - Human Resources -
--------------------------------------------
          -=- Employee Detials -=-
============================================
Employee #:   913740
First Name:   Jennifer
Last Name:    Beals
Gross Salary: Jennifer
Last Name:    31.25
============================================

Press any key to close this window . . .

Records and Records

A Record as a Type

A record can be involved with other records as normally as a type can be. For example, a property of a record can be a record type. Here is an example:

public record Identification
{
    public string? Abbreviation { get; set; }
    public string? GeneralName  { get; set; }
    public string? ISOName      { get; set; }
}

public record State
{
    public Identification Identification { get; set; }
    public string? Capital                { get; set; }
    public long   Area                   { get; set; }
    public long   Population             { get; set; }
}

Records and Inheritance

Records support inheritance, but a record can be derived only from another record. This means that you must first have a record that would serve as parent. Here is an example:

public record Employee
{
    public int    EmployeeNumber { get; set; }
    public string? FirstName      { get; set; }
    public string? LastName       { get; set; }
    public double HourlySalary   { get; set; }
}

public record Manager : Employee
{
    public string? JobTitle         { get; set; }
    public bool   CanUnlockAccount { get; set; }
}

Inheriting from Object

If you have a good functioning class that lays a foundation that can benefit other objects, unfortunately, you cannot derive a record from it. As a result, the following code will produce an error:

public class Vehicle
{
    public int Doors { get; set; }
}

public record Tractor : Vehicle
{
    public string? ModelName    { get; set; }
    public float   EnginePower  { get; set; }
    public int     LiftCapacity { get; set; }
    public int     MowerHeight  { get; set; }
    public decimal Price        { get; set; }
}

This error is because you cannot create a record that is based on a class. There is an exception. Remember that every time you create a class, the class is automatically derived from the object type. That concept also applies to structures and records. In fact, when you create a record, you can indicate that it derives from object. Here is an example:

Title = "Tractor Characteristics";

Tractor machine = new Tractor();

machine.ModelName    = "MW9724";
machine.EnginePower  = 21.5f;
machine.LiftCapacity = 754;
machine.MowerHeight  = 60;
machine.Price        = 13_050;

Console.WriteLine("Tractor Characteristics");
Console.WriteLine("=============================");
Console.WriteLine($"Model Name:     {machine.ModelName}");
Console.WriteLine($"Engine Power:   {machine.EnginePower} hp");
Console.WriteLine($"Lift Capacity:  {machine.ModelName} lbs");
Console.WriteLine($"Mower Height:   {machine.MowerHeight}");
Console.WriteLine($"Price:          {machine.Price:C}");
Console.WriteLine("=============================");

public record Tractor : object
{
    public string? ModelName    { get; set; }
    public float   EnginePower  { get; set; }
    public int     LiftCapacity { get; set; }
    public int     MowerHeight  { get; set; }
    public decimal Price        { get; set; }
}

This would produce::

Tractor Characteristics
=============================
Model Name:     MW9724
Engine Power:   21.5 hp
Lift Capacity:  MW9724 lbs
Mower Height:   60
Price:          $13,050.00
=============================

Press any key to close this window . . .

Probably the primary characteristic of the object class is that ot provides the primary features, mainly methods that all classes would need. One of these methods is the abiliby to find out whether two objects are equal. To make this happen, when you create a class, you can/must override the Equals() method. When it comes to records, the ability to compare two records objects is built in the record concept. Therefore, if you create a record, you cannot (you are not allowed to) override the Equals() method. If you do, you would receive an error.

Records and Classes

A Member of a Class Type

A member of a record, as a field or property, can be of the type of a class. You can use a .NET built-in class or you can create your own class. Then, on the body of a record, create a member whose type is a class. Here is an example:

public enum ChargeStatus { Negative, Neutral, Positive, Unknown }

// A class type
public class Nucleus
{
    // A nucleus is made of one or more protons and neutrons
    public int Protons  { get; set; }
    public int Neutrons { get; set; }
}

// A record layout
public record Atom
{
    // An atom is made of a nucleus (and one or more electrons)
    public Nucleus Nucleus { get; set; }
    public int Electrons   { get; set; }
    public ChargeStatus ChargeStatus { get; set; }
}

A Record for a Class Member

The type of a member of a class, such as a field or a property, can be based on a record. When creating the member, simply specify its type as an existing record. You can then use the member as regularly as necessary.

Practical LearningPractical Learning: Introducing the Size Mode of a Picture Box

  1. Click the IncomeTax.cs tab to acess its file and change the document as follows:
    namespace TaxPreparation5
    {
        public enum FilingStatus { Single, Married, Unknown }
    
        internal record Employee
        {
            public int    EmployeeNumber { get; set; }
            public string? FirstName      { get; set; }
            public string? LastName       { get; set; }
            public FilingStatus FilingStatus { get; set; }
        }
    
        internal record IncomeTax
        {
            public int      TaxIdentifier { get; set; }
            public Employee Employee      { get; set; } = new Employee();
            public double   GrossSalary   { get; set; }
    
            public double TaxAmount
            {
                get
                {
                    double amount = 0.00;
    
                    switch (Employee.FilingStatus)
                    {
                        case FilingStatus.Single:
                            if( (GrossSalary >= 0.00) && (GrossSalary <= 135.00))
                            {
                                amount = 0.00;
                            }
                            else if ((GrossSalary > 135.00) && (GrossSalary <= 712.00))
                            {
                                amount = (GrossSalary - 135.00) * 3.10 / 100.00;
                            }
                            else if ((GrossSalary > 712.00) && (GrossSalary <= 1288.00))
                            {
                                amount = ((GrossSalary - 712.00) * 5.25 / 100.00) + 17.88;
                            }
                            else // if (GrossSalary > 1288.00)
                            {
                                amount = ((GrossSalary - 1288.00) * 5.70 / 100.00) + 48.17;
                            }
                            break;
    
                        case FilingStatus.Married:
                            if ((GrossSalary >= 0.00) && (GrossSalary <= 308.00))
                            {
                                amount = 0.00;
                            }
                            else if ((GrossSalary > 308.00) && (GrossSalary <= 1462.00))
                            {
                                amount = (GrossSalary - 308.00) * 3.10 / 100.00;
                            }
                            else if ((GrossSalary > 1462.00) && (GrossSalary <= 2615.00))
                            {
                                amount = ((GrossSalary - 1462.00) * 5.25 / 100.00) + 35.77;
                            }
                            else // if (GrossSalary > 2615.00)
                            {
                                amount = ((GrossSalary - 2615.00) * 5.70 / 100.00) + 96.35;
                            }
                            break;
    
                        default:
                            amount = 0.00;
                            break;
                    }
    
                    return amount;
                }
            }
    
            public double NetSalary
            {
                get
                {
                    return GrossSalary - TaxAmount;
                }
            }
        }
    }
  2. Click the TaxPreparation.cs tab to access its file and change the document as follows:
    using static System.Console;
    using TaxPreparation5;
    
    IncomeTax Create()
    {
        Random rndNumber = new Random();
        IncomeTax income = new IncomeTax();
    
        WriteLine("==========================================");
        WriteLine(" - Amazing DeltaX - State Income Tax -");
        WriteLine("------------------------------------------");
        WriteLine("          -=- Kansas -=-");
        WriteLine("==========================================");
        WriteLine("Enter the information about the employee");
        income.Employee.EmployeeNumber = rndNumber.Next(100001, 999999);
        
        Write("Frist Name:             ");
        income.Employee.FirstName = ReadLine()!;
        Write("Last Name:              ");
        income.Employee.LastName = ReadLine()!;
        WriteLine("------------------------------------------");
        WriteLine("Options on Filing Status");
        WriteLine("1 - Single Person");
        WriteLine("2 - Married Person");
        Write("Type the filing status: ");
        string selection = ReadLine()!;
        WriteLine("------------------------------------------");
    
        if (selection == "1")
        {
            income.Employee.FilingStatus = FilingStatus.Single;
        }
        else if (selection == "2")
        {
            income.Employee.FilingStatus = FilingStatus.Married;
        }
        else
            income.Employee.FilingStatus = FilingStatus.Unknown;
    
        double salary = 0.00;
    
        try
        {
            Write("Gross Salary:           ");
            salary = double.Parse(ReadLine()!);
        }
        catch(FormatException exc)
        {
            WriteLine("It looks like you typed an invalid value for the gross income. The error produced is " + exc.Message);
        }
    
        income.TaxIdentifier = 1001;
        income.GrossSalary = salary;
    
        return income;
    }
    
    void Show(IncomeTax tax)
    {
        WriteLine("==========================================");
        WriteLine(" - Amazing DeltaX - State Income Tax -");
        WriteLine("------------------------------------------");
        WriteLine("          -=- Kansas -=-");
        WriteLine("==========================================");
        WriteLine($"Tax Identifier:  {tax.TaxIdentifier}");
        WriteLine("------------------------------------------");
        WriteLine($"Employee #:      {tax.Employee.EmployeeNumber}");
        WriteLine($"First Name:      {tax.Employee.FirstName}");
        WriteLine($"Last Name:       {tax.Employee.LastName}");
        WriteLine($"Filing Status:   {tax.Employee.FilingStatus}");
        WriteLine("------------------------------------------");
        WriteLine($"Gross Salary:    {tax.GrossSalary:f}");
        WriteLine($"Tax Amount:      {tax.TaxAmount:f}");
        WriteLine($"Net Pay:         {tax.NetSalary:f}");
        WriteLine("==========================================");
    }
    
    IncomeTax it = Create();
    
    Clear();
    
    Show(it);
  3. To execute the application, press Ctrl + F5
  4. When requested, type the values as followed and press Enter after each value:
    First Name: Vanessa
    Last Name: Swanson
    Filing Status: 1
    Gross Salary:  1648.83
    Here is the result:
    ==========================================
     - Amazing DeltaX - State Income Tax -
    ------------------------------------------
              -=- Kansas -=-
    ==========================================
    Tax Identifier:  1001
    ------------------------------------------
    Employee #:      610119
    First Name:      Vanessa
    Last Name:       Swanson
    Filing Status:   Single
    ------------------------------------------
    Gross Salary:    1648.83
    Tax Amount:      68.74
    Net Pay:         1580.09
    ==========================================
    
    Press any key to close this window . . .
  5. To close the window, press Q, and return to your programming environment

Records and Constructors

Introduction

Both the classes and the structures support constructors, quite in the same way (before C# 10.0, classes and structures dealt with constructors differently; for example, prior to C# 10.0, you were not allowed to create a default constructor (a constructor without a parameter) in a structure, starting with C# 10.0, you can). Constructors are a little different and particularly important in records.

So far, we didn't create constructors for our records but, like classes and constructors, a record can have constructors. In fact, if you create a record without a constructor, the compiler would create a default constructor for you. Still, if you want, you can create a default constructor in your record. Here is an example:

public record VehicleRegistration
{
    public VehicleRegistration()
    {
    }
}

Of course, when you use the default constructor to instantiate a record object, you can use the curly brackets to make a list of the properties of the record and provide a value for each. Here is an example:

VehicleRegistration vr = new VehicleRegistration()
{
    TagNumber = "HPR-385",
    Make = "Ford",
    Model = "Escape",
    Year = 2020
};

Console.Title = "Vehicle Registration";

Console.WriteLine("Vehicle Registration");
Console.WriteLine("=====================");
Console.WriteLine($"Tag #: {vr.TagNumber}");
Console.WriteLine($"Make:  {vr.Make}");
Console.WriteLine($"Model: {vr.Model}");
Console.WriteLine($"Year:  {vr.Year}");
Console.WriteLine("=====================");
        
public record VehicleRegistration
{
    int yr;
    string? _nbr;
    string? _make, _model;

    public VehicleRegistration()
    {
    }

    public string? TagNumber
    {
        get
        {
            return _nbr;
        }

        set
        {
            _nbr = value;
        }
    }

    public string? Make
    {
        get { return _make;  }
        set { _make = value; }
    }

    public string? Model
    {
        get { return _model; }
        set { _model = value; }
    }

    public int Year
    {
        get { return yr;  }
        set { yr = value; }
    }
}

public class Exercise
{
    static int Main()
    {
        

        return 54;
    }
}

This would produce:

Vehicle Registration
=====================
Tag #: HPR-385
Make:  Ford
Model: Escape
Year:  2020
=====================

Press any key to close this window . . .

A Parameterized Constructor

If you judge it necessary, when creating a record, you can create a constructor that takes at least one parameter. Here is an example:

public record VehicleRegistration
{
    string? _nbr;

    public VehicleRegistration(string number)
    {
    }
}

You can then use such a constructor to create an object of the record. Here is an example:

VehicleRegistration vr = new VehicleRegistration("HPR-385");

vr.Make = "Ford";
vr.Model = "Escape";
vr.Year = 2020;

Console.Title = "Vehicle Registration";

Console.WriteLine("Vehicle Registration");
Console.WriteLine("=====================");
Console.WriteLine($"Tag #: {vr.TagNumber}");
Console.WriteLine($"Make:  {vr.Make}");
Console.WriteLine($"Model: {vr.Model}");
Console.WriteLine($"Year:  {vr.Year}");
Console.WriteLine("=====================");

public record VehicleRegistration
{
    int yr;
    string? _nbr;
    string? _make, _model;

    public VehicleRegistration(string number)
    {
        _nbr = number;
    }

    . . .
}

As mentioned with classes (and valid for structures), if you create a constructor in a record and that constructor takes at least one parameter, the default constructor disappears. The solution is to manually create various constructors because, as we saw with methods, records support method overloading. To do this, create constructors that takes different numbers or different types of parameters. You can then call each constructor as needed. Here are examples:

// Using the default constructor to create an object
VehicleRegistration vr = new VehicleRegistration()
{
    TagNumber = "HPR-385",
    Make      = "Ford",
    Model     = "Escape",
    Year      = 2020
};

// Using a constructor with one parameter
VehicleRegistration vehicle = new VehicleRegistration("937052")
{
    Make  = "Toyota",
    Model = "Corolla",
    Year  = 1998
};

// A constructor with two parameters
VehicleRegistration register = new VehicleRegistration("Buick", "LeSabre")
{
    TagNumber = "FKE-RLW",
    Year      = 2020
};

// A public constructor
VehicleRegistration registration = new VehicleRegistration("GH4-G88", "Lincoln", "Continental", 2006);

public record VehicleRegistration
{
    int yr;
    string? _nbr;
    string? _make, _model;

    public VehicleRegistration()
    {
        _nbr   = "000-000";
        _make  = "Collectible";
        _model = "Classic";
        yr     = 1960;
    }

    public VehicleRegistration(string number)
    {
        _nbr = number;
    }

    public VehicleRegistration(string make, string model)
    {
        _make  = make;
        _model = model;
    }

    public VehicleRegistration(string nbr, string make, string model, int year)
    {
        _nbr   = nbr;
        _make  = make;
        _model = model;
        yr     = year;
    }

    public string? TagNumber
    {
        get { return _nbr; }
        set { _nbr = value; }
    }

    public string? Make
    {
        get { return _make;  }
        set { _make = value; }
    }

    public string? Model
    {
        get { return _model; }
        set { _model = value; }
    }

    public int Year
    {
        get { return yr;  }
        set { yr = value; }
    }
}

Initializing a Property

We know that you can create a property with a set clause, in which case you can let the clients the record assign a desired value to the property after an object of the record has been created. Here are examples of such properties:

Canoe fun = new Canoe();

fun.Identification = 280_385;
fun.Make           = "Emotion";
fun.Model          = "Wasatch ";
fun.Length         = 13;
fun.Price          = 479.99;
        
Console.Title = "Canoe Registration";

Console.WriteLine("Canoe Registration");
Console.WriteLine("=====================");
Console.WriteLine($"Identifier: {fun.Identification}");
Console.WriteLine("---------------------");
Console.WriteLine($"Make:       {fun.Make}");
Console.WriteLine($"Model:      {fun.Model}");
Console.WriteLine($"Length:     {fun.Length}");
Console.WriteLine($"Price:      {fun.Price}");
Console.WriteLine("=====================");

internal record Canoe
{
    int id;
    int len;
    double cost;
    string? _make, _model;

    public int Identification
    {
        get { return id; }
        set { id = value; }
    }

    public string? Make
    {
        get { return _make;  }
        set { _make = value; }
    }

    public string? Model
    {
        get { return _model; }
        set { _model = value; }
    }

    public int Length
    {
        get { return len;  }
        set { len = value; }
    }

    public double Price
    {
        get { return cost; }
        set { cost = value; }
    }
}

This would produce:

Canoe Registration
=====================
Identifier: 280385
---------------------
Make:       Emotion
Model:      Wasatch
Length:     13
Price:      479.99
=====================

Press any key to close this window . . .

If you want to require that a certain property be given a value immediately when a record object is created, you must create a clause named init instead of a set clause. Here is an example:

internal record Canoe
{
    int id;

    public int Identification
    {
        get  { return id; }
        init { id = value; }
    }
}

After doing that, the property must receive a value immediately when the record is instantiated. To make this happen, you have two options. When you create an object of the record, you can use the curly brackets to initialize the object. This can be done as follows:

Canoe fun = new Canoe()
{
    Make           = "Emotion",
    Model          = "Wasatch ",
    Identification = 280_385,
    Length         = 13,
    Price          = 479.99
};
        
Console.Title = "Canoe Registration";

Console.WriteLine("Canoe Registration");
Console.WriteLine("=====================");
Console.WriteLine($"Identifier: {fun.Identification}");
Console.WriteLine("---------------------");
Console.WriteLine($"Make:       {fun.Make}");
Console.WriteLine($"Model:      {fun.Model}");
Console.WriteLine($"Length:     {fun.Length}");
Console.WriteLine($"Price:      {fun.Price}");
Console.WriteLine("=====================");
        
internal record Canoe
{
    int id;
    int len;
    double cost;
    string? _make, _model;

    public int Identification
    {
        get  { return id; }
        init { id = value; }
    }

    public string? Make
    {
        get { return _make;  }
        set { _make = value; }
    }

    public string? Model
    {
        get { return _model; }
        set { _model = value; }
    }

    public int Length
    {
        get { return len;  }
        set { len = value; }
    }

    public double Price
    {
        get { return cost; }
        set { cost = value; }
    }
}

An alternate solution is to create a constructor in the record. That constructor must have a parameter for he property. In the body of the constructor, assign the parameter to property or to its associated field. This can be done as follows:

Canoe fun  = new Canoe(280_385);

fun.Make   = "Emotion";
fun.Model  = "Wasatch ";
fun.Length = 13;
fun.Price  = 479.99;
        
Console.Title = "Canoe Registration";

Console.WriteLine("Canoe Registration");
Console.WriteLine("=====================");
Console.WriteLine($"Identifier: {fun.Identification}");
Console.WriteLine("---------------------");
Console.WriteLine($"Make:       {fun.Make}");
Console.WriteLine($"Model:      {fun.Model}");
Console.WriteLine($"Length:     {fun.Length}");
Console.WriteLine($"Price:      {fun.Price}");
Console.WriteLine("=====================");

internal record Canoe
{
    int id;
    int len;
    double cost;
    string? _make, _model;

    public Canoe(int number)
    {
        id = number;
    }

    public int Identification
    {
        get  { return id; }
        init { id = value; }
    }

    public string? Make
    {
        get { return _make;  }
        set { _make = value; }
    }

    public string? Model
    {
        get { return _model; }
        set { _model = value; }
    }

    public int Length
    {
        get { return len;  }
        set { len = value; }
    }

    public double Price
    {
        get { return cost; }
        set { cost = value; }
    }
}

In the same way, in a record, you can create various properties that have init clauses. You can have a mix of properties with set and init clauses. You can also create automatic properties that usse init clauses. Remember that, when you are creating an object of the record, you must use one of both options we reviewed to initialize an caption property. Here is an example:

Canoe fun  = new Canoe(280_385)
{
    Make = "Emotion",
    Model = "Wasatch "
};

fun.Length = 13;
fun.Price  = 479.99;
        
Console.Title = "Canoe Registration";

Console.WriteLine("Canoe Registration");
Console.WriteLine("=====================");
Console.WriteLine($"Identifier: {fun.Identification}");
Console.WriteLine("---------------------");
Console.WriteLine($"Make:       {fun.Make}");
Console.WriteLine($"Model:      {fun.Model}");
Console.WriteLine($"Length:     {fun.Length}");
Console.WriteLine($"Price:      {fun.Price}");
Console.WriteLine("=====================");

internal record Canoe
{
    public Canoe(int number)
    {
        Identification = number;
    }

    public int    Identification { get; init; }
    public string? Make           { get; init; }
    public string? Model          { get; init; }
    public int    Length         { get; set; }
    public double Price          { get; set; }
}

A Record With Positional Parameters

Introduction

We have learned that a record can contain one or more constructors. As a simple way to create a record, you can use the formula of a constructor. The formula to follow is:

options record name(parameter-name(s));

As always, start with one or more appropriate options. The most basic option is an access level, such as public. Use the record keyword to indicate that you are creating a record type. Add a name for the record. The name follows the rules of names of classes.

Our formula is as if you are creating a constructor of a record without creating a body delimited by curly brackets. Since a constructor is a method, you must apply parentheses to the name of the record. In the parentheses, create a parameter for each property you would have added if you had created the record in a classic way. End the statement with a semicolon. This technique is referred to as a record with positional parameters. Here is an example:

public record Battery(string PartNumber, string Identification, double Price);

This is the descritîon of a record (as a kind of class or structure) with three properties (two string properties and one double property).

The Properties of a Record With Positional Parameters

After creating a record as done above, you have a regular record equipped with properties and one constructor that takes a parameter for each property. Every property of such a record is an automatic property with a get and an init clauses. As a normal type, you can use this record to create an object and use it appropriately. For example, you can declare a variable of it and specify a value for each poisitional parameter. Remember that each parameter represents an init property. Therefore, while using a constructor, you must pass an argument for each parameter. Here is an example:

Battery power = new Battery("928359", "ACDelco Gold 94RAGM", 164.95);

Console.Title = "Vehicle Battery";

Console.WriteLine("Vehicle Battery");
Console.WriteLine("=======================================");
Console.WriteLine($"Part #:         {power.PartNumber}");
Console.WriteLine("---------------------------------------");
Console.WriteLine($"Identification: {power.Identification}");
Console.WriteLine($"Price:          {power.Price}");
Console.WriteLine("=======================================");

public record Battery(string PartNumber, string Identification, double Price);

This would produce:

Vehicle Battery
=======================================
Part #:         928359
---------------------------------------
Identification: ACDelco Gold 94RAGM
Price:          164.95
=======================================

Press any key to close this window . . .

Default Positional Parameters

Remember that you must provide a parameter for each property of the record, but each property must be initialized when an object is created. We saw that there are two ways to initialize the object, and one way is with curly brackets. To use this way, you can provide a default value for each parameter. Here is an example:

public record Battery(string PartNumber = "", string Identification = "", double Price = 0.00);

This time, you can use curly brackets to initialize the object. Here is an example:

Battery power = new Battery()
{
    PartNumber = "928359",
    Identification = "ACDelco Gold 94RAGM",
    Price = 164.95
};

Console.Title = "Vehicle Battery";

Console.WriteLine("Vehicle Battery");
Console.WriteLine("=======================================");
Console.WriteLine($"Part #:         {power.PartNumber}");
Console.WriteLine("---------------------------------------");
Console.WriteLine($"Identification: {power.Identification}");
Console.WriteLine($"Price:          {power.Price}");
Console.WriteLine("=======================================");

Console.WriteLine(default(bool));
        
public record Battery(string PartNumber = "", string Identification = "", double Price = 0.00);

With a Record Updated

A record is an immutable type. This means that, once you have created and initialized a record, you cannot change its value(s). What you can do is to make a copy of an existing record. To do this, create another record object and assign an existing record to it. You can then use the new record object like any variable. Here is an example:

Battery power = new Battery()
{
    PartNumber = "928359",
    Identification = "ACDelco Gold 94RAGM",
    Price = 164.95
};

Battery current = power;

Console.Title = "Vehicle Battery";

Console.WriteLine("Vehicle Battery");
Console.WriteLine("=======================================");
Console.WriteLine($"Part #:         {current.PartNumber}");
Console.WriteLine("---------------------------------------");
Console.WriteLine($"Identification: {current.Identification}");
Console.WriteLine($"Price:          {current.Price}");
Console.WriteLine("=======================================");

Console.WriteLine(default(bool));

public record Battery(string PartNumber = "", string Identification = "", double Price = 0.00);

You can then change the value of any property you want and apply that change to the copied record. To do this, you use a keyword named with. The formula to use it is:

record-declaration with { property-1 = value-1, property-2 = value-2, property-x = value-x};

Start with a record object, such as done when declaring a record variable. Add the with keyword followed by curly brackets. End the statement with a semi-colon. Between the curly brackets, type the name of a property of the record or a list of properties. Assign the desired but appropriate value to the property. If you want to specify the values of various properties, separate them with commas. When you perform this exercise, you would get a new record with values changed on the indicated properties. The properties you don't access would keep their values given in the original record object. Here is an example:

using static System.Console;

Battery power = new Battery()
{
    PartNumber = "928359",
    Identification = "ACDelco Gold 94RAGM",
    Price = 164.95
};

Battery current = power with { PartNumber = "479605" };

Title = "Vehicle Battery";

WriteLine("Vehicle Battery");
WriteLine("=======================================");
WriteLine($"Part #:         {current.PartNumber}");
WriteLine("---------------------------------------");
WriteLine($"Identification: {current.Identification}");
WriteLine($"Price:          {current.Price}");
WriteLine("=======================================");

WriteLine(default(bool));

public record Battery(string PartNumber = "", string Identification = "", double Price = 0.00);

This would produce:

Vehicle Battery
=======================================
Part #:         479605
---------------------------------------
Identification: ACDelco Gold 94RAGM
Price:          164.95
=======================================

Press any key to close this window . . .

Topics on Records

A New, a Varied, or a Dynamic Object

As seen in the previous section, to create a record object, you can use the name of the record on both sides of the assignment operator and apply the new operator to initialize the object. Here is an example:

Tractor machine = new Tractor();

internal record Tractor
{

}

As seen with classes and structures, if you use the name of the record to create the object, you can omit that name after the new operator. Here is an example:

Tractor machine = new();

internal record Ocean
{

}

You can also create a record object using the var or the dynamic keyword. In this case, you must use the name of the record after the new operator. Here are examples:

dynamic work = new Tractor();
Ocean water = new();
var power = new Tractor();

internal record Ocean
{

}

A Record Object as a Reference

As seen so far, after creating a record, when you create a record object, you can use the new operator to initialize the object. When you do that, the object is a reference type, like that of a class; and the object is stored in the computer's heap. To reinforce this, when creating the record, add the class keyword before the name of the record. Here is an example:

Tractor machine = new();

public record class Tractor
{
    public string? ModelName    { get; set; }
    public float   EnginePower  { get; set; }
    public int     LiftCapacity { get; set; }
    public int     MowerHeight  { get; set; }
    public decimal Price        { get; set; }
}

In the same way, if you are creating a positional parameter record and you want the compiler to treat it as a class, create it as record class. Here is an example:

Battery power = new()
{
    PartNumber = "928359",
    Identification = "ACDelco Gold 94RAGM",
    Price = 164.95
};

public record class Battery(string PartNumber = "", string Identification = "", double Price = 0.00);

A Record Object as a Value Type

We saw that, when you declare a variable of a primitive type (int, float, bool, etc), the value is stored in the stack memory. If you want to use many of such values, you can group them in something called a structure. After creating such a structure, you can declare a variable of it. The values of such a variable are stored in the computer's memory stack. When you create a record object, you may want the values of that object to be stored in the stack instead of the heap. To do this, when creating the record, type the struct keyword before the name of the record. Here is an example:

Canoe fun = new()
{
    Identification = 280_385,
    Make = "Emotion",
    Model = "Wasatch "
};

public record struct Canoe
{
    int id;
    string? _make;
    string? _model;
    int     len;
    double _price;

public Canoe(int number)
{
    Identification = number;
}

    public int Identification
    {
        get
        {
            return id;
        }
        init
        {
            id = value;
        }
    }

    public string? Make
    {
        get { return _make; }
        init { _make = value;  }
    }
    public string? Model
    {
        get { return _model; }
        init { _model = value; }
    }

    public int Length
    {
        get { return len; 
        }
        
        set { len = value;
        }
    }

    public double Price
    {
        get
        {
            return _price;
        }

        set
        {
            _price = value;
        }
    }
}

If you are creating the record with positional parameters and you want its objects to be stored in the stack, create it with record struct.

Practical LearningPractical Learning: Ending the Lesson


Previous Copyright © 2001-2023, FunctionX Wednesday 13 October 2021 Next