Sealed records inheriting from object should not have an EqualityContract #49350
-
Version Used: Visual Studio 16.8.1 Consider the following code: public sealed record Point(int X, int Y); Because
The The performance difference could be measurable, e.g. when sealed records are used as keys in dictionaries. Furthermore, because
the |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 1 reply
-
Measurements would be valuable.
That could break existing code on upgrade. |
Beta Was this translation helpful? Give feedback.
-
I ran several benchmarks using these two types: sealed record PointRecord(int X, int Y);
sealed class PointClass : IEquatable<PointClass>
{
public PointClass(int x, int y) => (X, Y) = (x, y);
public int X { get; init; }
public int Y { get; init; }
public override int GetHashCode() =>
EqualityComparer<int>.Default.GetHashCode(X) * -1521134295 + EqualityComparer<int>.Default.GetHashCode(Y);
public override bool Equals(object obj) => Equals(obj as PointClass);
public bool Equals(PointClass other) =>
other != null && EqualityComparer<int>.Default.Equals(X, other.X) && EqualityComparer<int>.Default.Equals(Y, other.Y);
} As is to be expected, using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System;
using System.Collections.Generic;
BenchmarkRunner.Run<Benchmark>();
public class Benchmark
{
private readonly PointRecord pr = new PointRecord(7, 11);
private readonly PointClass pc = new PointClass(7, 11);
[Benchmark(Baseline = true)]
public int PointRecordGetHashCode() => pr.GetHashCode();
[Benchmark]
public int PointClassGetHashCode() => pc.GetHashCode();
}
Like I said, this is as expected: after all, calculating the hash code of an integer is basically a no-op. Obviously, we wouldn't see such a speed-up when the record contains a few string members. I'm sure we would see similar results when we benchmark the Where it get's interesting, is when we benchmark a simple algorithm using these types as keys in a dictionary: using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System;
using System.Collections.Generic;
BenchmarkRunner.Run<Benchmark>();
public class Benchmark
{
private readonly Dictionary<PointRecord, int> prdict = new();
private readonly List<PointRecord> prkeys = new();
private readonly Dictionary<PointClass, int> pcdict = new();
private readonly List<PointClass> pckeys = new();
public Benchmark()
{
for (int x = 0; x < 100; x++)
{
for (int y = 0; y < 100; y++)
{
prkeys.Add(new PointRecord(x, y));
prdict[new PointRecord(x, y)] = x + y;
pckeys.Add(new PointClass(x, y));
pcdict[new PointClass(x, y)] = x + y;
}
}
}
[Benchmark(Baseline = true)]
public int CalculationWithPointRecord()
{
int sum = 0;
foreach (var key in prkeys)
{
sum += prdict[key];
}
return sum;
}
[Benchmark]
public int CalculationWithPointClass()
{
int sum = 0;
foreach (var key in pckeys)
{
sum += pcdict[key];
}
return sum;
}
}
We see only a 6% speed difference in a microbenchmark on types with an artificially high performance difference for their It seems to me that these performance differences in themselves are not high enough to warrant a code change. |
Beta Was this translation helpful? Give feedback.
-
Thanks for that link. It makes a lot of sense. |
Beta Was this translation helpful? Give feedback.
https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-08-24.md#omitting-unnecessary-synthesized-record-members