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);
}
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;
}
}
}
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");
}
}
}
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();
}
}
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}");
}
}
}
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);
}
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));
}
...
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);
}
...
Resources
- C# 9.0 on the record | .NET Blog
- Records - C# 9.0 specification proposals | Microsoft Docs
- C# 9: Record Types Introduction & Deep-Dive | Claudio Bernasconi
- Introducing C# 9: Records – Anthony Giretti's .NET blog
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);
}
}
}
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));
}
...
Resources
- How to test for reference equality (Identity) - C# Programming Guide | Microsoft Docs
- How to define value equality for a type - C# Programming Guide | Microsoft Docs
- IEquatable Interface (System) | Microsoft Docs
- EqualityComparer Class (System.Collections.Generic) | Microsoft Docs
- EqualityComparer.Default Property (System.Collections.Generic) | Microsoft Docs
Top comments (0)