2011-11-15 110 views
7

是否有可能使用集合属性在LINQ中GroupBy?LINQ GroupBy collection

例如

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;} 
} 

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

键:列表(){ “一”, “二”}值:T1,T2

重点:列表(){“one”,“three”} Value:t3

我的理解是匿名类型不是通过引用进行比较,而是通过比较其公共属性上的相等性。

但是,实际结果是三组:

键:列表(){ “一”, “二”}值:T1

键:列表(){ “一” 字, “二”}值:T2

键:列表(){“一”,“三化”}值:T3

如果这是不可能的,是有办法得到我想要的结果?

有希望解释清楚...

回答

0

问题是,列表不完全相同。它比较分组的平等性,并且您有两个新的List<string> s,它们并不完全相等。你可以,但是,加入由哈希代码串,从而会产生正确的结果:

tests.GroupBy(t => String.Join(string.Empty, t.Children.Select(c => c.GetHashCode().ToString()))); 
+0

谢谢 - 我明白(我的代码示例是从我真正的问题简化了)。请参阅我的修改后的代码示例 – TheNextman

+1

我已经更新了我的答案以适用于您的现有代码。请看看它。它也不需要自定义的'IEqualityComparer'。 – doctorless

+0

如果散列码碰巧是相同的,这会产生非常奇怪的结果。 – Caramiriel

4

默认情况下,GroupBy会通过列表(这是引用类型)分组时使用引用相等。

由于您每次都有新的列表实例,它们并不相同。

然而,有一个GroupBy它可以让你指定自定义IEqualityComparer,这样就可以实现自己的比较字符串列表,例如方式的overload

为了实现这个,这里有很多other threads关于比较两个列表。

+0

谢谢 - 我明白(我的代码示例是从我真正的问题简化了)。请参阅我的修改后的代码示例 – TheNextman

+1

我的答案仍然适用,您需要创建一个自定义'IEqualityComparer'来比较两个字符串列表中每个成员的相等性。然后,将它作为第二个参数传递给'GroupBy'。 – wsanville

+0

您是否认为自定义的IEqualityComparer对于一次性的LINQ分组有点太过分了,这可能不需要两次? – doctorless

2

您得到3个组的原因是因为List<T>实现了与默认引用相等的相等性,而不是通过考虑任何两个列表之间包含元素的“序列相等性”。如果你想要这样的语义,你必须自己实现一个IEqualityComparer<IList<T>>(或类似的),并使用接受一个相等比较器的重载将其注入到GroupBy查询中。这里有一个sample实现(对于数组,不是列表,但容易适应)。

如果您熟悉设置平等(秩序和重复无关),你很幸运:你可以直接使用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();