C#

Explaining C# Records with Examples

In this post on Explaining C# Records with Examples, I will explain a new feature of C# 9.0 called Records. Basically, Record is a new feature of C#, so you need to install the latest version of Visual Studio in order to use it. After installing Visual Studio, create a console application in C# and open the project properties. After that, select the target framework as .NET 5.0 in the application tab as shown below.

Explaining C# Records with Examples
Set Target Framework to .NET 5.0

What are Records?

Record is a construct in C# that allows users to create objects. However, records are immutable and behave like a value. Although records behave like values, they are not value types. In fact, records are a reference type. It must be remembered that for records the content matters. In other words, we compare records by their contents. Also, we can still make records mutable, but records are better suited for creating immutable types.

Example of Immutable Records

Consider the following program of creating immutable records. Here, the use of init-only properties makes the record immutable and we can not change the values of properties. Hence, assigning the values after the creation of the record object is not possible and it will give a compile error. However, we can pass the record object as a parameter to a function where it can be used to create another object using with-expression.

using System;
namespace RecordDemo4
{
    record X
    {
        public int a { get; init; }
        public int b { get; init; }
    }
    class Program
    {
        static X Change(X ob)
        {
            X ob1 = ob with { b = ob.b * 2 };
            return ob1;
        }
        static void Main(string[] args)
        {
            X ob = new X { a = 100, b = 200 };
            Console.WriteLine("Property values of first record object: ");
            Console.WriteLine($"a={ob.a}, b={ob.b}");

            // ob.a = 150; ob.b = 300;
            Console.WriteLine("Property values of another record object: ");
            X ob2 = Change(ob);
            Console.WriteLine($"a={ob2.a}, b={ob2.b}");
        }
    }
}

Output

Immutable Records
Immutable Records

Why Do We Need Records?

Now that, we already have classes and structs in C# language to create user-defined types, so why another construct is needed? As an illustration, let us consider the following examples.

Comparing Classes and Records

The most important difference between classes and records is that the members of a record are implicitly public. Hence, we need not use the public keyword to access the record members. The following example will make it clear.

Example of Comparing Class, Struct, and Record Objects

The following example compares the three constructs – the class, the struct, and the record. By looking at the comparison of the objects of these three constructs, it is evident that the objects of the class are compared on the basis of the reference whereas the objects of the records are compared on the basis of content.

 using System;

namespace RecordsDemo1
{
    class Item
    {
        public int ItemNo { get; set; }
        public string ItemName { get; set; }
    }

    struct MyItem
    {
        public int ItemNo { get; set; }
        public string ItemName { get; set; }
        public static bool operator==(MyItem x, MyItem y)
        {
            return x.Equals(y);
        }
        public static bool operator !=(MyItem x, MyItem y)
        {
            return !x.Equals(y);
        }
    }

    record AnotherItem
    {
        public int ItemNo { get; set; }
        public string ItemName { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var ob1 = new Item();
            var ob2 = new MyItem();
            var ob3 = new AnotherItem();

            var ob4 = new Item();
            var ob5 = new MyItem();
            var ob6 = new AnotherItem();

            ob1.ItemNo = 1;
            ob1.ItemName = "Box";

            ob2.ItemNo = 2;
            ob2.ItemName = "Fan";

            ob3.ItemNo = 3;
            ob3.ItemName = "Bulb";

            ob4.ItemNo = 1;
            ob4.ItemName = "Box";


            ob5.ItemNo = 2;
            ob5.ItemName = "Fan";

            ob6.ItemNo = 3;
            ob6.ItemName = "Bulb";

            Console.WriteLine(ob1 == ob4);
            Console.WriteLine(ob2 == ob5);
            Console.WriteLine(ob3== ob6);

        }
    }
}

Output

Class, Struct, and Record
Class, Struct, and Record

Using With-expressions with Records

We can change a specific property of a record using with-expressions. Basically, with-expression provides a convenient way to change the value of specific properties of a record while the rest of the properties remain the same. Moreover, if we do it inside a method, then the record remains unaltered outside the scope of the method.

Consider the following example, in which we pass the object of the Item record as a parameter in a static method called GetDiscount() and the method only changes the price property. Additionally, the change is not reflected in the Main() method since the records are passed by value.

Example of Using With-Expression

using System;
namespace RecordsDemo2
{
    record Item
    {
        public int ItemNo { get; set; }
        public string ItemName { get; set; }
        public double price { get; set; }
}
class Program
{
    static void GetDiscount(Item c)
     {
            double discount = 0.12;
            double p = c.price;
            var r = c with { price = p - p * discount };
            //Inside the function
            Console.WriteLine("Record Inside the function...");
            Console.WriteLine($"Item No.: {r.ItemNo}  Item Name: {r.ItemName} Item Price: {r.price}");
        }
    static void Main(string[] args)
    {
            Item ob = new Item();
            ob.ItemNo = 11;
            ob.ItemName = "Fan";
            ob.price = 1600;

            //Before Discount
            Console.WriteLine("Record before calling the function...");
            Console.WriteLine($"Item No.: {ob.ItemNo}  Item Name: {ob.ItemName} Item Price: {ob.price}");
            GetDiscount(ob);
            //After Discount
            Console.WriteLine("Record after calling the function...");
            Console.WriteLine($"Item No.: {ob.ItemNo}  Item Name: {ob.ItemName} Item Price: {ob.price}");
        }
}
}

Output

Changing Property of Record using With-expression
Changing Property of Record using With-expression

Implementing Inheritance with Records

As you know that classes can be inherited and we can achieve run-time polymorphism using virtual and override keywords, likewise we can implement inheritance using records and also achieve run-time polymorphism in the same manner.

Example of Implementing Inheritance

In the following example, the Employee and Student records are inherited from the Person record. Also, both of these records override the method Show(). Moreover, they use the base keyword in a similar way as the classes do.

using System;
namespace RecordDemo3
{
    record Person
    {
        public string pname;
        public int age;
        public Person()
        {
            pname = "abc";
            age = 20;
        }
        public virtual void show()
        {
            Console.WriteLine("Show() method of Person record!");
            Console.WriteLine($"Name: {pname} Age: {age}");
        }
    }
    record Employee: Person
    {
        public string organization;
        public Employee()
        {
            organization = "XYZ Ltd.";
        }
        public override void show()
        {
            base.show();
            Console.WriteLine("Show() method of Employee record!");
            Console.WriteLine($"Organization: {organization}");
        }
    }
    record Student:Person
    {
        public string course;
        public Student()
        {
            course = "MBA";
        }
        public override void show()
        {
            base.show();
            Console.WriteLine("Show() method of Student record!");
            Console.WriteLine($"Course: {course}");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Person r;
            r = new Employee();
            r.show();
         
            r = new Person();
            r.show();

            r = new Student();
            r.show();

            Console.WriteLine("n");
            var v = new Employee();
            v.show();
            Console.WriteLine("n");
            var v1 = new Student();
            v1.show();
         }
    }
}

Output

Implementing Inheritance with Records
Implementing Inheritance with Records

What are Positional Records?

We can also create records using Positional Arguments. Precisely, if you wish to pass the record data values using constructor arguments, then you can use positional records. Consider the following examples.

Example of Positional Records

using System;
namespace RecordDemo5
{
    public record X(int a, int b);
    public record Employee(string empname, string designation);
    class Program
    {
        static void Main(string[] args)
        {
            var v1 = new X(a: 10, b: 20);
            var v2 = new Employee(empname: "Suman", designation: "Clerk");

            Console.WriteLine("First Record: ");
            Console.WriteLine($"a={v1.a}, b={v1.b}");
            Console.WriteLine("Second Record: ");
            Console.WriteLine($"Name={v2.empname}, Designation={v2.designation}");
        }
    }
}

Output

Positional Records
Positional Records

Summary

In this article on Explaining C# Records with Examples, you learned how to use records for creating objects. You can find more information on Records here.

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *