Запись C# 9: ToHashSet() не дает желаемого результата

#c# #c#-9.0

Вопрос:

 public static class TestRecordTypes
{
    public static void RunTest()
    {
        List<Person> people = new List<Person>()
        {
            new Person("John", "Doe", "home"),
            new Person("John", "Doe", "Away"),
            new Person("Billy", "Doe", "home"),
            new Person("Billy", "Doe", "home"),
            new Person("Batty", "Bo", "home"),
        };

        var peopleHash = people.ToHashSet();

        Debug.WriteLine($"{peopleHash}");
    }        
}

public record Person(string FirstName, string LastName)
{
    string _location;

    public Person(string firstName, string lastName, string location):
        this(firstName, lastName)
    {
        _location = location;
    }
}
 

С помощью этого теста я надеялся, что peopleHash в списке будет только 3 записи, но я получаю 4, так как равенство, по-видимому, включает параметр non init location .

Есть ли способ получить список из 3 записей, которые я хочу, где сравнение выполняется только для свойств инициализации и игнорирует дополнительный параметр местоположения?

Комментарии:

1. Вы всегда можете реализовать IEqualityComparer<Person> и передать это ToHashSet

2. Я думаю, что мог бы, но если я делаю это, я могу также использовать стандартный класс. Я чувствую, что должен быть декоратор, который я могу использовать, чтобы заставить компилятор игнорировать поле местоположения.

3. @John Вы можете написать генератор исходного кода и пользовательский атрибут для этого.

Ответ №1:

Вы хотите изменить IEquatable<Person> реализацию. Для этого вы можете добавлять Equal и переопределять GetHashCode в record :

 public record Person(string FirstName, string LastName)
{
    string _location;

    public Person(string firstName, string lastName, string location) :
        this(firstName, lastName) => _location = location;

    public virtual bool Equals(Person other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return FirstName == other.FirstName amp;amp; LastName == other.LastName;
    }

    public override int GetHashCode() => HashCode.Combine(FirstName, LastName);
}
 

затем тест проходит:

 [TestMethod]
public void HashTest()
{
    List<Person> people = new List<Person>()
    {
        new ("John", "Doe", "home"),
        new ("John", "Doe", "Away"),
        new ("Billy", "Doe", "home"),
        new ("Billy", "Doe", "home"),
        new ("Batty", "Bo", "home"),
    };

    var peopleHash = people.ToHashSet();
    Assert.AreEqual(3, peopleHash.Count);
}
 

Комментарии:

1. В конце концов, независимо от того, как вы снимете шкуру с этого кота, вам всегда придется где-то переопределить Equals и получить код. Однако мне кажется, что делать это в самой записи удобнее.

2. Да, я согласен, но для меня тип записи был бы более полезен, если бы он создавал эти методы, основанные только на неизменяемых свойствах, описанных: Лицо с публичной записью(имя строки, фамилия строки)

3. @John Вы можете отправить билет с предложением по адресу github.com/dotnet/csharplang

Ответ №2:

Используя IEqualityComparer вот так

 using System;
using System.Collections.Generic;
using System.Linq;

public static class TestRecordTypes
{
    public static void Main()
    {
        List<Person> people = new List<Person>()
        {
            new Person("John", "Doe", "home"),
            new Person("John", "Doe", "Away"),
            new Person("Billy", "Doe", "home"),
            new Person("Billy", "Doe", "home"),
            new Person("Batty", "Bo", "home"),
        };

        var peopleHash = people.ToHashSet(new MyEqualityComparer());

        Console.WriteLine(peopleHash.Count());
    }        
}

public record Person(string FirstName, string LastName)
{
    string _location;

    public Person(string firstName, string lastName, string location):
        this(firstName, lastName)
    {
        _location = location;
    }
}

class MyEqualityComparer : IEqualityComparer<Person>
{
    public bool Equals(Person p1, Person p2)
    {
        if (p1 == null amp;amp; p2 == null)
           return true;
        else if (p1 == null || p2 == null)
           return false;
        else if(p1.FirstName == p2.FirstName amp;amp; p1.LastName == p2.LastName)
                            
            return true;
        else
            return false;
    }

    public int GetHashCode(Person p)
    {
        string s = p.FirstName   p.LastName;
        return s.GetHashCode();
    }
}
 

Комментарии:

1. Спасибо, это работает наверняка, но, на мой взгляд, не имеет смысла использовать запись в первом случае. Так как это будет работать и для структуры/класса

2. Как задокументировано корпорацией Майкрософт: «Мы рекомендуем использовать класс EqualityComparer<T> вместо реализации интерфейса IEqualityComparer<T><T>, поскольку класс EqualityComparer<T><T> проверяет равенство с помощью метода IEquatable<T><T>.Equals вместо метода Object.Equals. »

3. @TimSchmelter Я должен признать, что для меня это тоже не имело большого смысла, но с Microsoft никогда не знаешь наверняка.