【问题标题】:SortList duplicated key, but it shouldn'tSortedList 重复键,但不应该
【发布时间】:2011-06-06 01:03:42
【问题描述】:

我有一个实现 IList 接口的类。我需要此列表的“排序视图”,但无需修改它(我无法直接对 IList 类进行排序)。

当原始列表被修改时,这些视图将被更新,保持项目排序。因此,我介绍了一种 SortList 创建方法,该方法创建一个 SortList,其中包含原始列表中包含的特定对象的比较器。

这里是sn-p的代码:

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

【问题讨论】:

  • 我忘了说这只发生在某些对象值上(大约 5%)。因此,虽然原始列表包含 100 个项目,但“排序视图”仅包含 95 个项目。
  • propX.CompareTo(propY) 返回 0 时,您不能尝试通过设置断点来调试您的GenericPropertyComparer 吗?因此,您将能够了解比较器是否工作正确以及值是否实际上等于...
  • @digEmAll 你明白了!我注意到 IComparable.CompareTo 不调用 GetHashCode!哎哟!我需要对 SortList IComparer 函数进行一些说明。现在我明白了为什么每个人都试图在 SortList 中复制键:他们想要对可以复制的键进行排序(在我的例子中,视图“ApplicationName”是重复的,但不是“ApplicationPath”)。
  • 对您的锁定的次要反馈 - 如果在锁定获取之前或期间失败(极端情况),您尝试释放您没有的锁定。这与提出的问题无关 - 只是需要注意。
  • @Marc Gravell 我从未考虑过 Monitor.Enter 失败的情况(只是抛出 ArgumenNullException)。怎么可能?

标签: c# sorting duplicates


【解决方案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++;

 }

在删除部分也是如此。

【讨论】:

  • 数据实际上是由单个线程访问的(至少在创建视图的进程上)。您的代码几乎是等效的,因为 Lock() 例程执行 Monitor.Enter(SyncRoot),但允许我以更复杂的方式限定独占对象访问范围。跨度>
  • 无论如何,mSortListView 有一个字符串类型的键,不明白哪个字典正在使用您提供的自定义 gethashcode 作为键的对象。无论如何,在 gethashcode 上调用 base 并不是一个好主意,因为您必须对有意义的属性进行哈希处理。
【解决方案2】:

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

【讨论】:

  • IMO 在比较器中使用GetHashCode 对我来说似乎有点奇怪,考虑到HashCode 对于两个不同的对象可能是相等的,这可能是错误的。看看我的答案以获得另一种解决方案。
  • 是的。我改变了设计:现在通用参数 T 应该从 IComparable 派生,以避免 GetHashCode() 比较。
  • @Luca:是的,这样更好。无论如何,我仍然认为使用SortedList 来获取按项目的单个属性排序的列表(需要解决关键唯一性约束的复杂性),这对我来说似乎是一个过度工作。按需对原始列表进行排序 IMO 更简单:)
  • 绝对取决于添加/删除元素的频率(这需要排序操作才能获得更新的“视图”)。
  • 是的,如果你经常添加/删除你是对的。在这种情况下,排序列表更好。另一种解决方案是在您已排序的列表上使用二进制搜索删除/添加,因此基本上您将获得与 SortedList 相同的性能。
【解决方案3】:

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

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

因此,例如,您的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;
}

编辑:

如果您需要经常从已排序的集合中添加/删除项目,也许使用 SortedList 会更好,但 SortedList 的问题是它需要唯一的键,在您的情况下您无法保证。

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

Implementation of sorted IList&lt;T&gt; that doesn't require unique values

【讨论】:

  • 根据您的评论编辑;-)
猜你喜欢
  • 2023-03-20
  • 2016-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-21
  • 2015-10-29
  • 1970-01-01
相关资源
最近更新 更多