【C#】Play records
Tags: csharp
Published: 2020-12-13 02:34:55

## 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
```cs
namespace RecordSample
{
    public record RecordSample(int Id, string Name);
}
```

It is almost the same as this.

### ClassSample.cs
```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;
        }
    }
}
```

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

### Progra.cs
```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");
        }
    }
}
```
### Inheritence
The record type is also a class, so it can inherit and become a concrete class.

#### IRecordSample.cs
```cs
namespace RecordSample
{
    public interface IRecordSample
    {
        void Message();
    }
}
```
#### RecordSample.cs
```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}");
        }
    }
}
```

### 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
```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);
}
```

### Comparison
The record type instances use their values to compare.

#### Program.cs
```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));
        }
...
```

### with expressions
I can create partially modified instances by "with".

#### Program.cs
```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);
        }
...
```

### Resources
* [C# 9.0 on the record | .NET Blog](https://devblogs.microsoft.com/dotnet/c-9-0-on-the-record/)
* [Records - C# 9.0 specification proposals | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/records)
* [C# 9: Record Types Introduction & Deep-Dive | Claudio Bernasconi](https://www.claudiobernasconi.ch/2020/11/07/csharp-9-record-types-introduction-and-deep-dive/)
* [Introducing C# 9: Records – Anthony Giretti's .NET blog](https://anthonygiretti.com/2020/06/17/introducing-c-9-records/)

## Equality
Because I want to know how to make a custom class behave as same as the record type, I decompile by [ILSpy](https://marketplace.visualstudio.com/items?itemName=icsharpcode.ilspy-vscode).

And I can know some things about equality.

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

### Implelent
I try to imitate the decompiled code.

#### ClassSample.cs
```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);
        } 
    }
}
```

I try comparing.

#### Program.cs
```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));
        }
...
```
### Resources
* [How to test for reference equality (Identity) - C# Programming Guide | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/how-to-test-for-reference-equality-identity)
* [How to define value equality for a type - C# Programming Guide | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type)
* [IEquatable<T> Interface (System) | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/api/system.iequatable-1?view=net-5.0)
* [EqualityComparer<T> Class (System.Collections.Generic) | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.equalitycomparer-1?view=net-5.0)
* [EqualityComparer<T>.Default Property (System.Collections.Generic) | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.equalitycomparer-1.default?view=net-5.0#System_Collections_Generic_EqualityComparer_1_Default)