【问题标题】:LINQ GroupBy collectionLINQ GroupBy 集合
【发布时间】:2011-11-15 15:12:16
【问题描述】:

是否可以在 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

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

希望能解释清楚...

【问题讨论】:

    标签: c# linq linq-to-objects


    【解决方案1】:

    默认情况下,GroupBy 在按列表(即引用类型)分组时将使用引用相等。

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

    但是,GroupBy 中的 overload 允许您指定自定义 IEqualityComparer,以便您可以实现自己的方式来比较字符串列表。

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

    【讨论】:

    • 谢谢 - 我明白这一点(我的代码示例是从我的实际问题中简化的)。请看我修改后的代码示例
    • 我的回答仍然适用,您需要创建一个自定义IEqualityComparer 来比较两个字符串列表中每个成员的相等性。然后,将其作为第二个参数传递给GroupBy
    • 对于可能不需要两次的一次性 LINQ 分组,您是否认为自定义 IEqualityComparer 有点过多?
    【解决方案2】:

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

    如果您对 set 相等感到满意(顺序和重复无关),那么您很幸运:您可以直接使用HashSet&lt;T&gt; 和提供的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();
    

    【讨论】:

      【解决方案3】:

      问题在于列表并不完全相同。它正在比较分组的相等性,并且您有两个新的List&lt;string&gt;s,它们并不完全相等。但是,您可以通过哈希码连接字符串,这将产生正确的结果:

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

      【讨论】:

      • 谢谢 - 我明白(我的代码示例是从我的实际问题中简化的)。请看我修改后的代码示例
      • 我已更新我的答案以使用您现有的代码。请看一下。它也不需要自定义IEqualityComparer
      • 如果哈希码恰好相同,这会产生非常奇怪的结果。
      【解决方案4】:

      我认为没有内置方法。

      在这里查看 Jon Skeet 的回答:

      Any chance to get unique records using Linq (C#)?

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-01-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多