Enumerable.Except не использует мой пользовательский компаратор

2

Я пытаюсь использовать метод except с пользовательским сопоставлением равенства, но он не работает.

Мой сравнительный коэффициент равенства:

public class BusinessObjectGuidEqualityComparer<T> : IEqualityComparer<T> where T : BusinessObject
{
    #region IEqualityComparer<T> Members

    /// <summary>
    /// Determines whether the specified objects are equal.
    /// </summary>
    /// <param name="x">The first object of type <paramref name="T"/> to compare.</param>
    /// <param name="y">The second object of type <paramref name="T"/> to compare.</param>
    /// <returns>
    /// <see langword="true"/> If the specified objects are equal; otherwise, <see langword="false"/>.
    /// </returns>
    public bool Equals(T x, T y)
    {
        return (x == null && y == null) || (x != null && y != null && x.Guid.Equals(y.Guid)); 
    }

    /// <summary>
    /// Returns a hash code for this instance.
    /// </summary>
    /// <param name="obj">The object to get the hash code.</param>
    /// <returns>
    /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
    /// </returns>
    /// <exception cref="T:System.ArgumentNullException">
    /// The type of <paramref name="obj"/> is a reference type and <paramref name="obj"/> is null.
    /// </exception>
    public int GetHashCode(T obj)
    {
        if (obj == null)
        {
            throw new ArgumentNullException("obj");
        }

        return obj.GetHashCode();
    }

    #endregion
}

Мое исключение:

BusinessObjectGuidEqualityComparer<Area> comparer = new BusinessObjectGuidEqualityComparer<Area>();
IEnumerable<Area> toRemove = this.Areas.Except(allocatedAreas, comparer);
IEnumerable<Area> toAdd = allocatedAreas.Except(this.Areas, comparer);

Странная вещь: событие, которое я предоставляю своему пользовательскому сопоставлению равенства, используется по умолчанию, поэтому что я делаю неправильно?

Спасибо за помощь.

  • 0
    Re ваш комментарий; Я обновлю свой пример, чтобы показать мой тестовый стенд, который работает ...
  • 0
    (просто в стороне - возможная проблема показывает важность предоставления полного примера кода ...)
Теги:
linq
extension-methods

3 ответа

4
Лучший ответ

Подобно Marc, я просто тестировал это, все называется просто отлично, я предполагаю, что вы попали в отложенное выполнение LINQ, обратите внимание на ToArray в моем коде.

Обратите внимание, что при прослеживании этого я заметил, что GetHashCode никогда не вызывается для нулевых объектов в компараторе.

Имейте в виду, что у MiscUtil есть замечательный способ сделать это, встроенный в строку: Могу ли я указать мой явный тип компаратора inline?

Или вы можете адаптировать это значение к Except: Отличительный список объектов на основе произвольного ключа в LINQ

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

namespace ConsoleApplication1 {

    public class BusinessObject {
        public Guid Guid { get; set; }
    }

    public class BusinessObjectGuidEqualityComparer<T> : IEqualityComparer<T> where T : BusinessObject {
        #region IEqualityComparer<T> Members

        public bool Equals(T x, T y) {
            return (x == null && y == null) || (x != null && y != null && x.Guid.Equals(y.Guid));
        }

        /// </exception>
        public int GetHashCode(T obj) {
            if (obj == null) {
                throw new ArgumentNullException("obj");
            }

            return obj.GetHashCode();
        }

        #endregion
    }

    class Program {
        static void Main(string[] args) {

            var comparer = new BusinessObjectGuidEqualityComparer<BusinessObject>();

            List<BusinessObject> list1 = new List<BusinessObject>() {
                new BusinessObject() {Guid = Guid.NewGuid()},
                new BusinessObject() {Guid = Guid.NewGuid()}
            };

            List<BusinessObject> list2 = new List<BusinessObject>() {
                new BusinessObject() {Guid = Guid.NewGuid()},
                new BusinessObject() {Guid = Guid.NewGuid()},
                null,
                null,
                list1[0]
            };

            var toRemove = list1.Except(list2, comparer).ToArray();
            var toAdd = list2.Except(list1, comparer).ToArray();

            // toRemove.Length == 1
            // toAdd.Length == 2
            Console.ReadKey();
        }
    }
}
  • 0
    Это оно! Я понял, что члены IEqualityComparer вызываются, когда я выполняю его, поэтому я попытался toRemove.ToList (). ForEach (...); и это работает.
3

Try:

public int GetHashCode(T obj) {
    return obj == null ? 0 : obj.Guid.GetHashCode();
}

Ваш хэш-код должен соответствовать равенству (или, по крайней мере, не противоречить ему); и ваше равенство говорит, что "нули равны, в противном случае сравнивают guid". Внутри я ожидаю, что Except использует HashSet<T>, что объясняет, почему получение GetHashCode right настолько важно.


Здесь моя тестовая установка (с использованием выше GetHashCode), которая отлично работает:

public abstract class BusinessObject {
    public Guid Guid { get; set; }
}
class Area : BusinessObject {
    public string Name { get; set; }
    static void Main() {
        Guid guid = Guid.NewGuid();
        List<Area> areas = new List<Area> {
            new Area { Name = "a", Guid = Guid.NewGuid() },
            new Area { Name = "b", Guid = guid },
            new Area { Name = "c", Guid = Guid.NewGuid() },
        };
        List<Area> allocatedAreas = new List<Area> {
            new Area { Name = "b", Guid = guid},
            new Area { Name = "d", Guid = Guid.NewGuid()},
        };
        BusinessObjectGuidEqualityComparer<Area> comparer =
             new BusinessObjectGuidEqualityComparer<Area>();
        IEnumerable<Area> toRemove = areas.Except(allocatedAreas, comparer);
        foreach (var row in toRemove) {
            Console.WriteLine(row.Name); // shows a & c, since b is allocated
        }
    }
}

Если ваша версия не работает, вам придется публиковать что-то о том, как вы ее используете, поскольку она отлично работает для меня (см. выше).

  • 0
    В документации IEqualityComparer определено, что GetHashCode выдает это исключение. Итак, я предполагаю, что я реализовал это Corrent. Но, в любом случае, это не решит мою проблему, члены IEqualityComparer вообще не вызваны.
  • 0
    Мое предположение, споткнувшись об отсроченном исполнении.
Показать ещё 2 комментария
1

Методы в вашем сопоставлении равенства не совпадают. Вы сравниваете GUID объектов, но метод GetHashCode использует реализацию по умолчанию, основанную на ссылке, а не на GUID. Поскольку разные экземпляры получат разные хэш-коды, хотя они имеют один и тот же идентификатор GUID, метод Equals никогда не будет использоваться.

Получить хэш-код для GUID в методе GetHashCode, поскольку это то, что вы сравниваете:

public int GetHashCode(T obj) {
    if (obj == null) {
        throw new ArgumentNullException("obj");
    }
    return obj.Guid.GetHashCode();
}
  • 0
    Да, вы правы, но это еще не решает проблему, потому что вызывается ли GetHashCode или Equals.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню