Joachim
Joachim

Reputation: 859

Ignore private member variables when doing equality checks with records in C#

I have a record definition where I would like to only check equality on public/specific member variables in the record definition. I haven't found a way to do this without making a custom Equals function and I would prefer not to that if there is another nicer solution. Any reflections on whether private member variables in records is a bad pattern or not, is also appreciated.

Example of record where equality is true:

        public record Test
        {
            public string Variable { get; init; }

            public Test(string someValue)
            {
                Variable = someValue;
            }
        }

        [Fact]
        public void RecordTest()
        {
            var test1 = new Test("hello");
            var test2 = new Test("hello");

            Assert.Equal(test1, test2); // Passes
        }

Example of record where I would like it to be true, but it's not:

       public record Test
        {
            // I believe this is causing it to fail, can it be ignored somehow?
            private readonly List<string> _list = new();

            public string Variable { get; init; }

            public Test(string someValue)
            {
                Variable = someValue;
                _list.Add(someValue);
            }
        }

        [Fact]
        public void RecordTest()
        {
            var test1 = new Test("hello");
            var test2 = new Test("hello");

            Assert.Equal(test1, test2); // Fails
        }

Upvotes: 7

Views: 2176

Answers (3)

chtenb
chtenb

Reputation: 16194

Like others suggested, the intended way to solve this is by manually implementing the Equals method. If writing the Equals manually is really too cumbersome in your case or sensitive to mistakes, you can also consider wrapping the member in a type that implements equality as always true.

public readonly struct IgnoreEquals<T>(T value) {
    public T Value { get; init; } = value;
    public override bool Equals(object? obj) => true;
    public override int GetHashCode() => 0;
    public override string? ToString() => Value?.ToString();
}

Then in your record types:

record Foo {
    private IgnoreEquals<int> _ignoreThisMemberInEquals = new(0);
}

This way the compiler generated Equals will still work. Of course the price you have to pay is that you have to type .Value everytime you want to access the value.

Upvotes: 0

Youssef13
Youssef13

Reputation: 4993

What about creating your own class that wraps your list, and implement your own object Equals and GetHashCode overrides, as well as implementing IEquatable interface. Then use your own wrapper in the record. Something like:

public class ListWrapper : IEquatable<ListWrapper>
{
    private readonly List<string> _list = new();

    public void Add(string item) => _list.Add(item);

    public bool Equals(ListWrapper other)
    {
        return _list.SequenceEqual(other._list);
    }

    // you may or may not want to override object Equals and GetHashCode.
}


public record Test
{
    private readonly ListWrapper _list = new();

    public string Variable { get; init; }

    public Test(string someValue)
    {
        Variable = someValue;
        _list.Add(someValue);
    }
}

Upvotes: 0

Sulexa
Sulexa

Reputation: 36

In the microsoft documentation you can read this:

For records, value equality means that two variables of a record type are equal if the types match and all property and field values match. For other reference types such as classes, equality means reference equality. That is, two variables of a class type are equal if they refer to the same object. Methods and operators that determine equality of two record instances use value equality.

The list here needs to have the same reference for the equality to work. I think using an equality comparer or override equal is the best way to go.

Upvotes: 1

Related Questions