LINQ GroupBy集合

7
在LINQ中,是否可以使用集合属性进行GroupBy操作呢?
例如:
void Main()
{
    var t1 = new Test() { Children = new List<string>() { "one", "two" } };
    var t2 = new Test() { Children = new List<string>() { "one", "two" } };
    var t3 = new Test() { Children = new List<string>() { "one", "three" }        };

    var tests = new List<Test>() { t1, t2, t3 };
    var anon =  from t in tests
                select new
                {
                    Children = t.Children
                };

    anon.GroupBy(t => t.Children).Dump();
}

public class Test
{
    public List<string> Children {get;set;}
}

在这个例子中,我希望有两个组:

键:List() { "one", "two" } 值:t1、t2

键:List() { "one", "three" } 值:t3

我的理解是匿名类型不是按引用比较,而是通过比较它们的公共属性相等来比较。
然而,实际结果是三个组:

键:List() { "one", "two" } 值:t1

键:List() { "one", "two" } 值:t2

键:List() { "one", "three" } 值:t3

如果这不可能,有没有办法得到我想要的结果呢?
希望我表述清楚了...
4个回答

4
默认情况下,GroupBy 在对列表(引用类型)进行分组时会使用引用相等性。由于每次都有新的列表实例,它们是不相等的。然而,GroupBy 有一个重载,允许您指定自定义的IEqualityComparer,以便您可以实现自己的比较字符串列表的方式。为了实现这一点,这里有许多其他线程关于如何比较两个列表。

谢谢 - 我明白了(我的代码示例是从我的实际问题简化而来的)。请查看我修改后的代码示例。 - TheNextman
1
我的回答仍然适用,您需要创建一个自定义的IEqualityComparer,比较两个字符串列表中每个成员的相等性。然后将其作为第二个参数传递给GroupBy - wsanville
你是否认为为了一个可能不会被使用两次的一次性LINQ分组而考虑自定义的IEqualityComparer有点过头了? - CassOnMars

2
你得到了3组的原因是因为List<T>使用默认引用相等性实现相等性,而不是考虑任意两个列表之间包含的元素的“序列相等性”。如果你想要这样的语义,你需要自己实现一个IEqualityComparer<IList<T>>(或类似)并将其注入到GroupBy查询中,使用接受相等比较器的重载方法。这里是一个示例实现(针对数组而不是列表,但很容易适应)。
如果你熟悉集合相等性(顺序和重复无关),那么你很幸运:你可以直接使用HashSet<T>和提供的CreateSetComparer方法进行比较器实现:
  var t1 = new Test { Children = new HashSet<string> { "one", "two" } };
  var t2 = new Test { Children = new HashSet<string> { "one", "two" } };
  var t3 = new Test { Children = new HashSet<string> { "one", "three" } };

  var tests = new List<Test> { t1, t2, t3 };

  // Only two groups: { one, two } and { one, three }
  tests.GroupBy(t => t.Children, HashSet<string>.CreateSetComparer())
       .Dump();

0

0
问题在于这两个列表并不完全相同。它正在比较组合的相等性,而你有两个新的List<string>,它们并不完全相等。但是,你可以通过哈希代码连接字符串,这将产生正确的结果:
tests.GroupBy(t => String.Join(string.Empty, t.Children.Select(c => c.GetHashCode().ToString())));

谢谢 - 我明白了(我的代码示例是从我真正的问题简化过来的)。请看一下我修改后的代码示例。 - TheNextman
1
我已经更新了我的答案,使其与您现有的代码配合使用。请看一下。它也不需要自定义的 IEqualityComparer - CassOnMars
如果哈希码恰好相同,这将生成非常奇怪的结果。 - Caramiriel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接