SortList дублированный ключ, но он не должен - PullRequest
0 голосов
/ 09 января 2011

У меня есть класс, который реализует интерфейс IList. Мне требуется «отсортированное представление» этого списка, но без его изменения (я не могу отсортировать напрямую класс IList).

Эти представления должны обновляться при изменении исходного списка, сохраняя элементы отсортированными. Итак, я ввел метод создания SortList, который создает SortList, в котором есть средство сравнения для конкретного объекта, содержащегося в исходном списке.

Вот фрагмент кода:

public class MyList<T> : ICollection, IList<T> 
{
    public SortedList CreateSortView(string property)
    {
        try
        {
            Lock();

            SortListView sortView;

            if (mSortListViews.ContainsKey(property) == false)
            {
                // Create sorted view
                sortView = new SortListView(property, Count);
                mSortListViews.Add(property, sortView);

                foreach (T item in Items)
                    sortView.Add(item);
            } else
                sortView = mSortListViews[property];

            sortView.ReferenceCount++;
            return (sortView);
    }
    finally
    {
        Unlock();
    }
}

public void DeleteSortView(string property)
{
    try
    {
        Lock();

        // Unreference sorted view
        mSortListViews[property].ReferenceCount--;
        // Remove sorted view
        if (mSortListViews[property].ReferenceCount == 0)
            mSortListViews.Remove(property);
    }
    finally
    {
        Unlock();
    }
}

protected class SortListView : SortedList
{
    public SortListView(string property, int capacity)
        : base(new GenericPropertyComparer(typeof(T).GetProperty(property, BindingFlags.Instance | BindingFlags.Public)), capacity)
    {               
    }

    public int ReferenceCount = 0;

    public void Add(T item)
    {
        Add(item, item);
    }

    public void Remove(T item)
    {
        base.Remove(item);
    }

    class GenericPropertyComparer : IComparer
    {
        public GenericPropertyComparer(PropertyInfo property)
        {
            if (property == null)
                throw new ArgumentException("property doesn't specify a valid property");
            if (property.CanRead == false)
                throw new ArgumentException("property specify a write-only property");
            if (property.PropertyType.GetInterface("IComparable") == null)
                throw new ArgumentException("property type doesn't IComparable");

            mSortingProperty = property;
        }

        public int Compare(object x, object y)
        {
            IComparable propX = (IComparable)mSortingProperty.GetValue(x, null);
            IComparable propY = (IComparable)mSortingProperty.GetValue(y, null);
            return (propX.CompareTo(propY));
        }

        private PropertyInfo mSortingProperty = null;
    }

    private Dictionary<string, SortListView> mSortListViews = new Dictionary<string, SortListView>();
}

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

Кажется, это хорошо с первого взгляда, но это создает мне проблемы, поскольку дает следующее исключение при добавлении элементов в список сортировки:

System.ArgumentException: элемент уже добавлен. Введите словарь: «PowerShell_ISE [C: \ Windows \ sysWOW64 \ WindowsPowerShell \ v1.0 \ PowerShell_ISE.exe]» Добавляемый ключ: «PowerShell_ISE [C: \ Windows \ system32 \ WindowsPowerShell \ v1.0 \ PowerShell_ISE.exe]»

Как видно из сообщения об исключении, выданного SortedListView.Add(object), строковое представление ключа (объекта элемента списка) отличается (обратите внимание на путь исполняемого файла).

Почему SortList дает мне это исключение?

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

public override int GetHashCode()
{
    return (
        base.GetHashCode() ^
        mApplicationName.GetHashCode() ^
        mApplicationPath.GetHashCode() ^
        mCommandLine.GetHashCode() ^ 
        mWorkingDirectory.GetHashCode()
    );
}

Ответы [ 3 ]

1 голос
/ 09 января 2011

Если я вас правильно понял, ваша цель - просто просмотреть список, отсортированный по свойству объекта.

Тогда зачем использовать SortedList, для которого требуются уникальные ключи, когда вы легко можете получить свой результат, используя LINQ OrderBy (или если вы используете .net 2.0 List.Sort())?

Следовательно, например, ваш CreateSortView может быть реализован следующим образом:
(без блокировки, try / finally и подсчета ссылок)

public IList<T> CreateSortView(string property)
{
    IList<T> sortView;
    if (mSortListViews.ContainsKey(property) == false)
    {
        // Create sorted view
        sortView = this.OrderBy(x => x, new GenericPropertyComparer<T>(property)).ToList();
        mSortListViews.Add(property, sortView);
    }
    else
    {
        sortView = mSortListViews[property];
    }
    return sortView;
}

С GenericPropertyComparer реализовано следующим образом:

class GenericPropertyComparer<T> : IComparer<T>
{
    public GenericPropertyComparer(string propertyName)
    {
        var property = typeof(T).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public);

        if (property == null)
            throw new ArgumentException("property doesn't specify a valid property");
        if (property.CanRead == false)
            throw new ArgumentException("property specify a write-only property");
        if (property.PropertyType.GetInterface("IComparable") == null)
            throw new ArgumentException("property type doesn't IComparable");

        mSortingProperty = property;
    }

    public int Compare(T x, T y)
    {
        IComparable propX = (IComparable)mSortingProperty.GetValue(x, null);
        IComparable propY = (IComparable)mSortingProperty.GetValue(y, null);

        return (propX.CompareTo(propY));
    }

    private PropertyInfo mSortingProperty = null;
}

EDIT:

Если вам нужно свободно добавлять / удалять элементы из вашей отсортированной коллекции, возможно, лучше использовать SortedList, но проблема с SortedList заключается в том, что ему нужны уникальные ключи, и в вашем случае вы не можете этого гарантировать. *

В любом случае, вы можете использовать пользовательский отсортированный список, который не нуждается в уникальных значениях, посмотрите на ссылку ниже для простой реализации:

Реализация отсортированного IList<T>, который не требует уникальных значений

1 голос
/ 09 января 2011

Мне кажется, это проблема многопоточности. Я не вижу, что делает функция Lock () в вашем коде, но я думаю, что вам повезет больше, если окружить код доступа к словарю стандартной блокировкой:

lock(this){
SortListView sortView;
if (mSortListViews.ContainsKey(property) == false) {
            // Create sorted view
            sortView = new SortListView(property, Count);
            mSortListViews.Add(property, sortView);

            foreach (T item in Items)
                sortView.Add(item);
        } else
            sortView = mSortListViews[property];
        sortView.ReferenceCount++;

 }

и то же самое в снимаемой части.

0 голосов
/ 09 января 2011

Благодаря комментарию digEmAll я нашел быстрое решение: реализация IComparer должна возвращать 0 только для объектов, действительно равных!

Итак:

public int Compare(object x, object y)
{
    IComparable propX = (IComparable)mSortingProperty.GetValue(x, null);
    IComparable propY = (IComparable)mSortingProperty.GetValue(y, null);
    int compare;

    if ((compare = propX.CompareTo(propY)) == 0) {
        if (x.GetHashCode() < y.GetHashCode())
            return (-1);
        else if (x.GetHashCode() > y.GetHashCode())
            return (+1);
        else return (0);
    } else
        return (compare);
}
...