使用LINQ GroupBy获取忽略属性的唯一集合

时间:2018-09-21 11:34:25

标签: c# linq

使用Rules的集合,我试图忽略Rules属性并创建唯一列表,创建另一个Site的集合。

public class Rule
{
    public int TestId { get; set; }
    public string File { get; set; }
    public string Site { get; set; }
    public string[] Columns { get; set; }
}

因此,如果我的收藏夹具有如下所示的值:

var rules = new List<Rule>
{
    new Rule { TestId = 1, File = "Foo", Site = "SiteA", Columns = new string[] { "ColA", "ColB" }},
    new Rule { TestId = 1, File = "Foo", Site = "SiteB", Columns = new string[] { "ColA", "ColB" }}
};

我想要最终结果

var uniqueRules = new List<Rule>
{
    new Rule { TestId = 1, File = "Foo", Site = null, Columns = new string[] { "ColA", "ColB" }}
};

尝试了如下所示的各种组合后,我仍然可以得到2个结果,如何获得预期的结果?

var uniqueRules = rules
    .GroupBy(r => new { r.TestId, r.File, r.Columns })
    .Select(g => g.Key)
    .Distinct()
    .ToList();

4 个答案:

答案 0 :(得分:6)

问题在于string[]并没有覆盖EqualsGetHashCode,这就是为什么在r.Columns仅比较引用的原因。您需要提供自定义IEqualityComparer<T>

public class RuleComparer : IEqualityComparer<Rule>
{
    public bool Equals(Rule x, Rule y)
    {
        if (object.ReferenceEquals(x, y)) return true;
        if (x == null || y == null) return false;
        if(!(x.TestId == y.TestId && x.File == y.File)) return false;
        return x.Columns.SequenceEqual(y.Columns);
    }

    // from: https://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode
    public int GetHashCode(Rule obj)
    {
        unchecked
        {
            int hash = 17;
            hash = hash * 23 + obj.TestId.GetHashCode();
            hash = hash * 23 + (obj.File?.GetHashCode() ?? 0);
            foreach(string s in obj.Columns)
                hash = hash * 23 + (s?.GetHashCode() ?? 0);
            return hash;
        }
    }
}

现在LINQ查询变得微不足道了:

List<Rule> uniqueRules = rules.Distinct(new RuleComparer()).ToList();

答案 1 :(得分:1)

这里有几个观察结果:

  1. GroupBy()将具有与执行Distinct()相同的效果。因此,要么创建将为您执行比较的EqualityComparer,要么只需执行GroupBy(),而无需同时执行两项操作。

  2. 分组后您将获得Key。您可能想要整个对象退回,所以如果您想要一个实际的.First(),请使用Rule,并且不在乎同一组中是否有多个对象。

  3. 规则是不同的,因为Columns是对不同数组的引用,它们不是按值进行比较,而是按引用进行比较。

要合并所有这些观察结果,如果不想编写自定义EqualityComparer并采用分组方式,则可以使用以下代码:

var uniqueRules = rules
        .GroupBy(r => new { r.TestId, r.File, Columns = string.Join(",", r.Columns) })
        .Select(r => r.First())
        .ToList();

这将仅对列使用字符串,使其成为还可以通过值进行比较的属性。

请注意,仅由于Columns是一个简单的字符串数组,才可能这样做。对于更复杂的类型,这不那么方便。

答案 2 :(得分:0)

我建议扩展您的类Rule,以实现以下equals方法:

public class Rule :IEquatable<Rule>
    {
        public int TestId { get; set; }
        public string File { get; set; }
        public string Site { get; set; }
        public string[] Columns { get; set; }

        public bool Equals(Rule other)
        {
            return TestId == other.TestId &&
                   string.Equals(File, other.File) &&
                   Equals(Columns, other.Columns);
        }
    }

如您所见,在比较两个类时,我们将忽略“站点”字段。这也使您可以灵活地将来更改比较。 然后使用:rules.Distinct();

答案 3 :(得分:0)

问题在于,尽管列看上去都相似new string[] { "ColA", "ColB" },但是列没有引用相同的对象,并且它们只有相等的数据。试试这个:

string[] cols = new string[] { "ColA", "ColB" };
var rules = new List<Rule>
{
    new Rule { TestId = 1, File = "Foo", Site = "SiteA", Columns = cols},
    new Rule { TestId = 1, File = "Foo", Site = "SiteB", Columns = cols}
};

现在您自己的查询应该可以正常工作:

var uniqueRules = rules
    .GroupBy(r => new { r.TestId, r.File, r.Columns })
    .Select(g => g.Key)
    .Distinct()
    .ToList();