SortedList重复键,但不应该

时间:2011-01-09 10:59:21

标签: c# sorting duplicates

我有一个实现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都将使用相同的操作进行更新。

这看起来很不错,但它会给我带来问题,因为它在向SortList添加项目时会给我以下异常:

  

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 个答案:

答案 0 :(得分:1)

在我看来,这是一个多线程问题。我无法看到你的代码中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++;

 }

和删除部分相同。

答案 1 :(得分:1)

如果我理解正确,你的目的只是为了查看你的列表,按对象的属性排序。

然后,当您使用LINQ SortedList(或者如果您使用.net 2.0 OrderBy)轻松获得结果时,为什么要使用需要唯一键的List.Sort()

因此,例如,您的CreateSortView可以通过这种方式实施:
(省略锁定,尝试/最后和引用计数)

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;
}

修改

如果你需要频繁地添加/删除已排序集合中的项目,可能使用SortedList会更好,但SortedList的问题是它需要唯一的密钥,在你的情况下你无法保证。

无论如何,您可以使用不需要唯一值的自定义排序列表,请查看下面的链接以获得简单的实现:

Implementation of sorted IList<T> that doesn't require unique values

答案 2 :(得分:0)

感谢 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);
}