【问题标题】:Why do I get a System.ArgumentException when invoking Sort(IComparer) on a List?为什么在列表上调用 Sort(IComparer) 时会出现 System.ArgumentException?
【发布时间】:2012-06-08 11:40:14
【问题描述】:

我使用自己的 IComparer 对列表进行排序,并且在运行应用程序(XNA 游戏)超过一个小时时效果很好。但是,突然之间,在使用自定义比较器调用排序方法时,有时会出现以下错误:

An unhandled exception of type 'System.ArgumentException' occured in mscorlib.dll
Additional Information: ArgumentException

这是抛出异常的那一行:

List<Continent> markets = new List<Continent>();
// filling the markets list ...
markets.Sort(new MarketCostCoverComparer(this)); 

这是我实现 IComparer 接口的类:

class MarketCostCoverComparer : IComparer<Continent> { 

    private Player player; 

    public MarketCostCoverComparer(Player player) { 
        this.player=player; 
    } 

    public int Compare(Continent c1, Continent c2) { 
        if(player.GetCostCovering(c1)<player.GetCostCovering(c2)) { 
            return +1; 
        } else if(player.GetCostCovering(c1)==player.GetCostCovering(c2)) { 
            return 0; 
        } else { 
            return -1; 
        } 
    } 

} 

这里有一些链接到比较器的方法...:

public float GetCostCovering(Continent continent) {
        // cover<1 => bad | cover>1 => good
        if(GetOilfieldTheoreticOutput(continent.Type, true)<continent.Economy.CurrentDemand) {
            return ((float)((GetOilfieldTheoreticOutput(continent.Type, true)*continent.Economy.CurrentPrice)))/(float)GetOilfieldCosts(continent.Type, true);
        } else {
            return ((float)((continent.Economy.CurrentDemand*continent.Economy.CurrentPrice)))/(float)GetOilfieldCosts(continent.Type, true);
        }
    }

public int GetOilfieldTheoreticOutput(ContinentType continent, bool drilled) {
        int total = 0;
        foreach(Oilfield oilfield in worldmap.Continents[(int)continent].Oilfields) {
            if(oilfield.Owner==this && oilfield.Drilled==drilled) {
                total+=oilfield.TheoreticOutput;
            }
        }
        return total;
    }

public int GetOilfieldCosts(ContinentType continent, bool drilled) {
        int total = 0;
        foreach(Oilfield oilfield in worldmap.Continents[(int)continent].Oilfields) {
            if(oilfield.Owner==this && oilfield.Drilled==drilled) {
                total+=oilfield.Costs;
            }
        }
        return total;
    }

这里是异常的截图:

在这里仔细查看 Locals/Stack-Trace(这是一个旧屏幕截图,但我会在接下来的几个小时内尝试重现此异常,以便展开跟踪):

【问题讨论】:

  • 你能提供异常的堆栈跟踪吗?
  • 1) 发布内部异常,如果有,或者至少是堆栈跟踪 2) 发布 GetCostCovering() 方法
  • 不幸的是没有堆栈跟踪,因为 Visual Studio 只是弹出一个带有上述两行的消息框。 GetCostCovering() 方法紧跟在 ...
  • 您是否从另一个线程更改此列表?那么,在排序时,您从另一个线程中删除/添加元素?
  • 好的,现在当你点击break时,你可以在Locals窗口中展开$exception对象。或者,应该显示一个托管调试助手的窗口(假设它已为ArgumentException 启用),指向触发异常的行,以便检查堆栈跟踪。

标签: c# list sorting exception


【解决方案1】:

问题在于您的 IComparer 的实现。它会返回不一致的结果,所以排序函数会抛出异常。

也许看看thisthis question 了解更多信息。

问题:

属性continent.Economy.CurrentDemandcontinent.Economy.CurrentPrice 没有副作用吗?

备注:

您的 IComparer 应该能够处理 null。来自docs

允许将 null 与任何类型进行比较,并且不会生成 使用 IComparable 时出现异常。排序时,null被认为是 比任何其他物体都小。

也许这是一个浮点问题,但这只是一个疯狂的猜测。所以也许你应该使用decimal 而不是float

【讨论】:

  • 为什么比较器会返回不一致的结果?
  • 一个值与自身比较不相等,或者一个值与另一个值重复比较会产生不同的结果
  • @Filip:我同意,这应该改写为“问题可能与您的 GetCostCovering 的实现有关”。我怀疑浮点数与自身比较不相等,因此假设计算是确定性的并且值在排序过程中不会改变,我看不出这段代码有明显的问题。
  • 新增截图显示System.Collections.Generic.ArraySortHelper中引发异常;并且此类引发 ArgumentException 的唯一方法是 QuickSort 实现引发 IndexOutOfRangeException;如果比较器以某种方式损坏,就会发生这种情况。
  • 所以建议是他的比较器在某种程度上被破坏了?我有一种感觉,OP已经知道了。此外,ArgumentException 可能会从代码中未显示的其他任何地方冒泡(例如所有属性)。
【解决方案2】:

这是我从史蒂夫(来自 XNA 论坛)那里得到的另一个答案:

显然,当您不为两个相同的对象返回 0 时,可能会发生这种情况。当它们都引用同一个对象时,GetCostCovering(c1) 是否有可能在任何时候都不等于 GetCostCovering(c2)?

为了安全起见,试着把 if (c1 == c2) return 0;在 Compare 方法的开头,看看效果如何。

非常感谢史蒂夫!

【讨论】:

  • 如果这两个参数引用同一个对象,并且GetCostCovering连续调用两次返回不同的结果,那么你需要调试它,而不是隐藏它恕我直言。
【解决方案3】:

如果我没记错的话,如果第一个参数小于第二个参数,IComparer.Compare 应该返回小于零。您在实现中似乎做了相反的事情,这可以解释异常。我无法解释为什么它在这次失败之前工作了很长时间......

【讨论】:

  • 但是提供IComparer.Compare 的全部目的是为了让您可以定义“第一个参数小于第二个参数”的含义。在这个实现中,使用了特定的成本函数,并且最终的比较使用了不同的运算符,这一事实既不存在也不存在。
  • 你说得对,我承认我没有仔细阅读代码。但是,查看 IComparer 的文档,它指出如果出现以下情况,则会引发 ArgumentException:“x 和 y 都没有实现 IComparable 接口。”再往下:“首选的实现是使用参数之一的 CompareTo 方法。”所以我建议在这种情况下看看 Continent 类,并实现 CompareTo。
  • 您不必自己实现 compare 方法 - 至少您不必这样做。 GetCostCovering 返回float,所以比较器可以是return player.GetCostCovering(c1).CompareTo(player.GetCostCovering(c2)),这样比较简单,但不能解决OPs问题。
  • @salocinx:这是一个多线程应用程序吗? worldmap.Continents 中的值是否可能在排序过程中发生变化?
  • 他在某处写了它的单线程到目前为止。我确信堆栈跟踪将提供更多见解
猜你喜欢
  • 1970-01-01
  • 2021-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-24
  • 2021-09-28
  • 1970-01-01
相关资源
最近更新 更多