【问题标题】:C# Code Contracts -- How to ensure that a collection of items contains items with unique properties?C# 代码合同——如何确保项目集合包含具有唯一属性的项目?
【发布时间】:2011-12-13 15:36:00
【问题描述】:

基本上,我有以下几点:

public class MyClass
{
    public MyClass(ICollection<MyObject> coll)
    {
        Contract.Requires(coll != null);
        Contract.Requires(Contract.ForAll(coll, obj => obj != null));
        Contract.Requires(Contract.ForAll(coll, obj => (????)); //What goes here?
    }
}

public class MyObject
{
    public object PropA { get; set; }
    public object PropB { get; set; }
}

要求是:

  • 集合中的所有 PropA 项目都是唯一的(没有重复)
  • 集合中的所有 PropB 项目都是唯一的(没有重复)

似乎无法弄清楚我的 Contract.ForAll(...) 声明在这里做什么。


奖励:如果我可以组合 Contract.ForAll(...) 语句而不破坏代码合同?

【问题讨论】:

  • 乍一看,函数的输入应该是 ISet 而不是 ICollection,但也许过于简单化了...
  • AVee 说了什么。我通常不会在Requires 中编码这样的复杂先决条件。如果您使用的是静态检查器,则无法证明这一点。最好使用像 Set 这样对先决条件进行编码的类。

标签: c# linq collections code-contracts


【解决方案1】:

我可能完全不在这里,从未使用过合同,但假设Contract.Requires 可以传递任意bool,你不能这样做:

Contract.Requires(coll.GroupBy(o => o.PropA).Count() == coll.Count);

PropB 也类似?

【讨论】:

  • 啊,是的,这是一个比我想象的更好的 groupby 版本。
  • @Chris:我也喜欢你的版本......它似乎可能更有效,因为一旦 LINQ 遇到超过其中一项。这个必须计算组的数量(所以它必须遍历整个集合......两次?)。无法确定哪个“更容易阅读”,因为此时两者之间的效率可能可以忽略不计。
  • 假设您记得 GroupBy 返回的内容(由于某种原因我通常不记得),那么我会说它们大致相同。无论如何,一个非常简短的评论可能在这里很有用,尽管它可以解释英语的要求(例如“PropA 在整个集合中必须是唯一的”),您可以很容易地在其中添加一行(例如“PropA 的组计数对象数量相等意味着它们是唯一的”)。
  • 我想如果效率和提早退出很重要,你也可以用IEnumerable.Any 做点什么。我正在想最好的办法,但至少要创建一个空的 HashSet 和 coll.Any(x=&gt;!hashset.Add(x.PropA))。只有当 add 返回 false 时,内部事物才会评估为 true - 即项目已经在哈希集中。它确实需要更多的内存来做,但如果这是一个考虑因素,我认为可能是通过 coll 的最少迭代。 (因为它只会遍历一次并在找到重复项时终止该遍历)。
  • 我已经更新了我的答案,提供了关于最后一个答案的更多细节。而且我认为我应该停止尝试用很多不同的方法来用 linq 做同样的事情,而是去做一些更有用的事情。 :)
【解决方案2】:

我相信以下方法应该可以解决问题:

Contract.Requires(
    Contract.ForAll(
        coll, 
        obj => (coll.Where(x=>x.PropA = obj.PropA).Count==1)
    )
);

理论是它只过滤 coll 到那些 PropA 的值与我们正在查看的对象相同的元素。应该只有其中之一(本身)。

你可以对 B 进行类似的重复。

理论上,结合 ForAll lambda 表达式是微不足道的,但我不确定你是否愿意。当然,最好知道哪个条件失败了,而不是把它们混为一谈,知道某事失败了,但实际上不是什么......

如果你可以在格式上留一点余地,你可以试试:

Contract.Requires(
    Contract.ForAll(
        coll.GroupBy(x=>x.PropA), 
        group => group.Count==1)
    )
);

这是一个类似的原则,但我认为会更有效地进行计数,因为 group by 和 count 会更有效(我认为 - 我没有测试过并且不熟悉 linq 方法的内部工作原理)。

另一种方法:

HashSet<object> propAValues = new HashSet<object>();
Contract.Requires(
    !coll.Any(x=>!hashset.Add(x.PropA))
);

这使用了一个哈希集,如果元素已经存在,则 Add 返回 false。在这种情况下,当 Add 生成 false(因此 lambda 表达式为 true)时,Any 将返回 true,因为它被否定,所以测试失败。

此方法是否合理可能取决于您的对象有多大(以及加倍对象集的潜在内存影响。然而,与此处的其他方法相比,它需要最少的迭代来终止(因为其他方法需要查看集合中的每个对象,可能会多次查看,而最后一个可能会在查看两个条目后停止)。

【讨论】:

  • 这行得通,但我希望有另一个比这更有效的 LINQ 调用/方法。
  • 我已经更新了第二段代码,它应该以不同的方式做同样的事情。它与您要求的格式不匹配,但我想不出如何以任何其他方式使其更快。
猜你喜欢
  • 2019-07-12
  • 2013-05-15
  • 2016-05-16
  • 1970-01-01
  • 2013-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多