Как использовать объединение C # LINQ, чтобы получить объединение пользовательского list1 со list2

#c# #linq #union #extension-methods

#c# #linq #объединение #методы расширения

Вопрос:

Я использую Enumerable.Union<TSource> метод для получения объединения пользовательского List1 с пользовательским List2. Но почему-то в моем случае это работает не так, как должно. Я получаю все элементы, также дублирующие один раз.

Я перешел по ссылке MSDN, чтобы выполнить работу, но все же я не могу добиться того же.

Ниже приведен код пользовательского класса:-

 public class CustomFormat : IEqualityComparer<CustomFormat>
{
    private string mask;

    public string Mask
    {
        get { return mask; }
        set { mask = value; }
    }

    private int type;//0 for Default 1 for userdefined

    public int Type
    {
         get { return type; }
         set { type = value; }
    }
    public CustomFormat(string c_maskin, int c_type)
    {
        mask = c_maskin;
        type = c_type;
    }

    public bool Equals(CustomFormat x, CustomFormat y)
    {
        if (ReferenceEquals(x, y)) return true;

        //Check whether the products' properties are equal. 
        return x != null amp;amp; y != null amp;amp; x.Mask.Equals(y.Mask) amp;amp; x.Type.Equals(y.Type);
    }

    public int GetHashCode(CustomFormat obj)
    {
        //Get hash code for the Name field if it is not null. 
        int hashProductName = obj.Mask == null ? 0 : obj.Mask.GetHashCode();

        //Get hash code for the Code field. 
        int hashProductCode = obj.Type.GetHashCode();

        //Calculate the hash code for the product. 
        return hashProductName ^ hashProductCode;
    }
}
  

Это я вызываю следующим образом:-

 List<CustomFormat> l1 = new List<CustomFormat>();
l1.Add(new CustomFormat("#",1));
l1.Add(new CustomFormat("##",1));
l1.Add(new CustomFormat("###",1));
l1.Add(new CustomFormat("####",1));

List<CustomFormat> l2 = new List<CustomFormat>();
l2.Add(new CustomFormat("#",1));
l2.Add(new CustomFormat("##",1));
l2.Add(new CustomFormat("###",1));
l2.Add(new CustomFormat("####",1));
l2.Add(new CustomFormat("## ###.0",1));

l1 = l1.Union(l2).ToList();

foreach(var l3 in l1)
{
    Console.WriteLine(l3.Mask   " "   l3.Type);
}
  

Пожалуйста, предложите подходящий способ добиться того же!

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

1. Это кажется странным, но ваш код работает, если вы а) предоставляете конструктор без параметров для CustomFormat и передаете экземпляр этого класса методу объединения — см. dotnetfiddle.net/YTVwTI . Тогда возникает вопрос, почему объединение игнорирует реализацию IEqualityComparer<CustomFormat> внутри класса.

Ответ №1:

Странность здесь в том, что ваш класс реализует IEqualityComparer<CustomClass> вместо IEquatable<CustomClass> . Вы могли бы передать другой экземпляр CustomClass , который будет использоваться в качестве средства сравнения, но было бы более идиоматично просто сделать CustomClass реализацию IEquatable<CustomClass> , а также переопределить Equals(object) .

Разница между IEquatable<T> и IEqualityComparer<T> заключается в том, что IEquatable<T> говорится: «Я знаю, как сравнить себя с другим экземпляром T «, тогда IEqualityComparer<T> как говорится: «Я знаю, как сравнить два экземпляра T «. Последнее обычно предоставляется отдельно — так же, как оно может быть предоставлено с Union помощью другого параметра. Очень редко тип реализуется IEqualityComparer<T> для своего собственного типа, тогда IEquatable<T> как в значительной степени должен использоваться только для сравнения значений одного и того же типа.

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

 public class CustomFormat : IEquatable<CustomFormat>
{
    public string Mask { get; set; }
    public int Type { get; set; }

    public CustomFormat(string mask, int type)
    {
        Mask = mask;
        Type = type;
    }

    public bool Equals(CustomFormat other)
    {
        if (ReferenceEquals(this, other))
        {
            return true;
        }
        return other != null amp;amp; other.Mask == Mask amp;amp; other.Type == Type;
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as CustomFormat);
    }

    public override int GetHashCode()
    {
        // Get hash code for the Name field if it is not null. 
        int hashProductName = Mask == null ? 0 : Mask.GetHashCode();

        //Get hash code for the Code field. 
        int hashProductCode = Type.GetHashCode();

        //Calculate the hash code for the product. 
        return hashProductName ^ hashProductCode;
    }
}
  

Теперь это не помогает, что (как отмечено в комментариях) документация для Enumerable.Union неверна. В настоящее время в нем говорится:

Средство сравнения равенства по умолчанию, Default , используется для сравнения значений типов, реализующих IEqualityComparer<T> универсальный интерфейс.

В нем должно быть что-то вроде:

Средство сравнения равенства по умолчанию, Default , используется для сравнения значений, когда конкретное IEqualityComparer<T> значение не указано. Если T реализовано IEquatable<T> , средство сравнения по умолчанию будет использовать эту реализацию. В противном случае он будет использовать реализацию Equals(object) .

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

1. Я попытался реализовать, но, похоже, это не решает проблему. Пожалуйста, посмотрите ссылку на код. Ошибка не отображается, но отображаются повторяющиеся элементы.

2. @JDoshi: эта скрипка все еще реализуется IEqualityComparer<T> . Приведенный мною код ( IEquatable<T> вместо реализации) работает с приведенным вами примером.

3. На самом деле я отредактировал ту же скрипку и поделился с вами ссылкой, которая изначально была предоставлена stuartd . Итак, вы случайно поделились неправильной скрипкой. Извините за ошибку: (

4.@JDoshi: ваш код не переопределяет Object.GetHashCode . Вместо этого он все еще реализуется IEqualityComparer<T>.GetHashCode . Если бы вы использовали код в моем ответе, все было бы в порядке — и точно так же вы бы увидели в этом проблему, если бы посмотрели на предупреждения компилятора (которые вы переопределяли Equals , но нет GetHashCode ). Всегда обращайте внимание на предупреждения.

Ответ №2:

Вам нужно передать экземпляр an IEqualityComparer в Union метод. Метод имеет перегрузку для передачи в вашем компараторе.

Самое простое и уродливое решение

 var comparer = new CustomFormat(null,0);

l1 = l1.Union(l2, comparer).ToList();
  

Вы допустили некоторые ошибки в своей реализации. Вы не должны реализовывать IEqualityComparer метод в своем type ( CustomFormat ), а в отдельном классе, например CustomFormatComparer .

В вашем type ( CustomFormat ) вы должны быть реализованы IEquatable .

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

1. Ссылка MSDN для объединения вводит в заблуждение, тогда, как говорится, «Средство сравнения равенства по умолчанию, Default, используется для сравнения значений типов, которые реализуют универсальный интерфейс IEqualityComparer<T> . Чтобы сравнить пользовательский тип данных, вам необходимо реализовать этот интерфейс и предоставить свои собственные методы GetHashCode и Equals для типа это не оговаривает, что это должно быть в отдельном классе.

2. @Jehof Не могли бы вы объяснить использование IEquatable в этом случае. Здесь IEquatable используется прямая реализация, а не IEqualityComparer ошибка, поскольку Union используется ошибка.

3. @JDoshi: Нет, это не так — реализация IEquatable<T> действительно является способом продвижения вперед.