使用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();
答案 0 :(得分:6)
问题在于string[]
并没有覆盖Equals
和GetHashCode
,这就是为什么在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)
这里有几个观察结果:
GroupBy()
将具有与执行Distinct()
相同的效果。因此,要么创建将为您执行比较的EqualityComparer
,要么只需执行GroupBy()
,而无需同时执行两项操作。
分组后您将获得Key
。您可能想要整个对象退回,所以如果您想要一个实际的.First()
,请使用Rule
,并且不在乎同一组中是否有多个对象。
规则是不同的,因为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();