DEV Community

loading...

【C#】Play records

Masui Masanori
Programmer, husband, father I love C#, TypeScript, etc.
・4 min read

Intro

From C# 9, I can use "record type".
This time, I will try using it.

Environments

  • .NET ver.5.0.101

Basic features

If a type only has init-only setter properties, I can write it like below.

RecordSample.cs

namespace RecordSample
{
    public record RecordSample(int Id, string Name);
}
Enter fullscreen mode Exit fullscreen mode

It is almost the same as this.

ClassSample.cs

namespace RecordSample.Models
{
    public class ClassSample
    {
        public int Id { get; private init; }
        public string Name { get; private init; }

        public ClassSample(int id, string name)
        {
            Id = id;
            Name = name;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

But it's not exactly the same when I instanciate them.

Progra.cs

namespace RecordSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // OK
            var r = new RecordSample(Id: 1, Name: "Hello");
            // Compile error
            var c1 = new ClassSample(Id: 1, Name: "Hello");
            // OK
            var c2 = new ClassSample(1, "Hello");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Inheritence

The record type is also a class, so it can inherit and become a concrete class.

IRecordSample.cs

namespace RecordSample
{
    public interface IRecordSample
    {
        void Message();
    }
}
Enter fullscreen mode Exit fullscreen mode

RecordSample.cs

using System;

namespace RecordSample
{
    public record RecordSample: IRecordSample
    {
        public int Id { get; }
        public string Name { get; }

        public RecordSample(int id, string name) => (Id, Name) = (id, name);
        public void Message()
        {
            Console.WriteLine($"Hello {Name}");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Serialize, Deserialize

I can use it for serializing or deserializing JSON.
And I also can use as a model class of Entity Framework Core.

Book.cs

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

[Table("Books")]
public record Book
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id{ get; init; }
    [Required]
    public string Name { get; init; }
    public Book(int id, string name) => (Id, Name) = (id, name);
}
Enter fullscreen mode Exit fullscreen mode

Comparison

The record type instances use their values to compare.

Program.cs

...
        static void Main(string[] args)
        {
            var r1 = new RecordSample(Id: 1, Name: "Hello");
            var r2 = new RecordSample(Id: 1, Name: "Hello");

            var c1 = new ClassSample(1, "Hello");
            var c2 = new ClassSample(1, "Hello");

            // True
            Console.WriteLine(r1.Equals(r2));
            // True
            Console.WriteLine(r1 == r2);
            // False
            Console.WriteLine(object.ReferenceEquals(r1, r2));

            // False
            Console.WriteLine(c1.Equals(c2));
            // False
            Console.WriteLine(c1 == c2);
            // False
            Console.WriteLine(object.ReferenceEquals(c1, c2));
        }
...
Enter fullscreen mode Exit fullscreen mode

with expressions

I can create partially modified instances by "with".

Program.cs

...
        static void Main(string[] args)
        {
            var r1 = new RecordSample(Id: 1, Name: "Hello");
            var r2 = r1 with { Name = "World" };

            // RecordSample { Id = 1, Name = Hello }
            Console.WriteLine(r1);
            // RecordSample { Id = 1, Name = World }
            Console.WriteLine(r2);
        }
...
Enter fullscreen mode Exit fullscreen mode

Resources

Equality

Because I want to know how to make a custom class behave as same as the record type, I decompile by ILSpy.

And I can know some things about equality.

  • implement "IEquatable<T>"
  • hold own type by "EqualityContract"
  • override "Equals(object? obj)" and "GetHashCode()"
  • override "==" and "!=" operators
  • use EqualityComparer<T>.Default to access properies

Implelent

I try to imitate the decompiled code.

ClassSample.cs

using System;
using System.Collections.Generic;

namespace RecordSample.Models
{
    public class ClassSample: IEquatable<ClassSample>
    {
        protected Type EqualityContract => typeof(ClassSample);

        public int Id { get; private init; }
        public string Name { get; private init; }
        public ClassSample(int id, string name) => (Id, Name) = (id, name);

        public override bool Equals(object? obj)
        {
            return Equals(obj as ClassSample);
        }
        public virtual bool Equals(ClassSample? obj)
        {
            if((obj as object) == null)
            {
                return false;
            }
            if(EqualityContract != obj.EqualityContract)
            {
                return false;
            }
            return EqualityComparer<int>.Default.Equals(Id, obj.Id) &&
                EqualityComparer<string>.Default.Equals(Name, obj.Name);
        }
        public static bool operator ==(ClassSample? r1, ClassSample? r2)
        {
            // Do not like "r1 == null" or stackoverflow will be occurred
            if((r1 as object) == null || (r2 as object) == null)
            {
                return false;
            }
            // Do not like "r1 == r2" or stackoverflow will be occurred
            if(object.ReferenceEquals(r1, r2))
            {
                return true;
            }
            return r1.Equals(r2);
        }
        public static bool operator !=(ClassSample? r1, ClassSample? r2)
        {
            return (r1 == r2) == false;
        }
        public override int GetHashCode ()
        {
            return (EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295) +
                (EqualityComparer<int>.Default.GetHashCode(Id) * -1521134295) +
                (EqualityComparer<string>.Default.GetHashCode(Name) * -1521134295);
        } 
    }
}
Enter fullscreen mode Exit fullscreen mode

I try comparing.

Program.cs

...
        static void Main(string[] args)
        {
            var c1 = new ClassSample(1, "Hello");
            var c2 = new ClassSample(1, "Hello");

            // True
            Console.WriteLine(c1.Equals(c2));
            // True
            Console.WriteLine(c1 == c2);
            // False
            Console.WriteLine(object.ReferenceEquals(c1, c2));
        }
...
Enter fullscreen mode Exit fullscreen mode

Resources

Discussion (0)