【问题标题】:MVC LINQ Union with compare IComparer vs IEqualityComparerMVC LINQ Union 与比较 IComparer 与 IEqualityComparer
【发布时间】:2015-08-07 12:44:46
【问题描述】:

我有一个示例类

public class Item 
{
    public int Id;
    public string Name;
    public int ItemParentId;
}

然后我将许多 Items 放入数据库,它们有一个 Id、Name 和 ItemParentId,但我还创建了一个新 Items 列表,它们有 Name、ItemParentId,但 Id = 0;

我确实选择了数据库中的所有项目到 list1。我用新的项目创建了新的 list2。

我想做这样的事情:

list1.Union(list2); // need to combine only with different ItemParentId

但问题是我只需要组合那些 ItemParentId 不相等的项目。 Linq Union 只让创建 IEqualityComparer,但是这个不适合。我也试过 IComparer,但 Union 不允许使用它。任何帮助将不胜感激。

列表示例和我想要的结果:

var list1 = { 
       Item { Id = 1, Name = "item1", ItemParentId = 100 },
       Item { Id = 2, Name = "item2", ItemParentId = 200 },
       Item { Id = 3, Name = "item3", ItemParentId = 300 },
       Item { Id = 4, Name = "item4", ItemParentId = 400 }
  } 

var list2 = new List<Item>{ 
       new Item { Id = 0, Name = "item5", ItemParentId = 500 },
       new Item { Id = 0, Name = "item6", ItemParentId = 300 },
       new Item { Id = 0, Name = "item7", ItemParentId = 400 },
  }

结果列表应包含 3 个项目,名称分别为“item1”、“item2”、“item3”、“item4”和“item5”

更新:

谢谢大家,在你们的帮助下,我设法按单个属性比较项目,但现在我必须通过其中两个来比较。其实我的小姐姐现在有10个属性,但我只需要比较两个,比较器看起来不错,我只想知道HashCode是做什么用的?

【问题讨论】:

  • 为什么 IEqualityComparer “不适合”?
  • 因为它比较两个对象,但我只需要比较对象参数,list1和list2之间的Id永远不会相等
  • @GrandaS 你可以写一个IEqualityComparer 来做任何你想做的事情,包括比较ItemParentId。但请注意,Union 仍会返回其中一个重复项。
  • 它以编码方式比较两个对象以比较它们,您需要根据 ItemParentId 比较两个 Item 对象,因此创建一个相等比较器来执行此操作。
  • @GrandaS 嗯。为什么结果应该包含“item3”?似乎应该这样做。

标签: c# list interface


【解决方案1】:

看起来就像IEqualityComparer 毕竟是你想要的:

public class Comparer : IEqualityComparer<Item>
{
    public bool Equals(Item x, Item y)
    {
        return x.ItemParentId == y.ItemParentId;
    }

    public int GetHashCode(Item obj)
    { 
        return obj.ItemParentId;
    }
}

调用代码:

var result = list1.Union(list2, new Comparer())

更新:如果您想比较多个属性,您可以更改比较器:

public class Comparer : IEqualityComparer<Item>
{
    public bool Equals(Item x, Item y)
    {
        return x.ItemParentId == y.ItemParentId 
            || x.Name == y.Name;
    }

    public int GetHashCode(Item obj)
    {
        unchecked 
        {
            int hash = 17;
            hash = hash * 23 + obj.ItemParentId.GetHashCode();
            hash = hash * 23 + obj.Name.GetHashCode();
            return hash;
        }
    }
}

要了解有关GetHashCode 实现的更多信息,请参阅this answer

要了解更多关于GetHashCode的信息一般请参阅theseanswers

您可能注意到,如果您只是 return 1GetHashCode 方法中的某些内容,您的代码仍然有效。如果你不实现GetHashCodeUnion 将调用Equals,它会工作但比GetHashCode 慢。 Implementing GetHashCode increases performance.

【讨论】:

  • GetHasCodeEquals 绝对应该基于同一件事。基本上,如果两个项目相等,它们应该具有相同的哈希码。
  • 如果我无法访问xy,我该怎么做?
  • @juharr 我在看herehere,他们的方法似乎和我一样。
  • 您可以访问obj,并且您的GetHashCode 应该基于objItemParentId,而不是Id,因为您比较的是xItemParentIdy。您的两个示例在 EqualsGetHasCode 方法中都使用了相同的属性。
  • @juharr 哎呀。我只是混淆了属性。感谢您指出了这一点。我更新了我的答案。
【解决方案2】:

如果是 linq-to-objects,那么您确实可以使用 IEqualityComparer

public class ByParentIdComparer : IEqualityComparer<Item>
{
  public bool Equals(Item x, Item y)
  {
    return x.ItemParentId == y.ItemParentId;
  }
  public int GetHashCode(Item obj)
  { 
    return obj.ItemParentId;
  }
}

然后:

list1.Union(list2, new ByParentIdComparer())

会起作用的。

这虽然不能很好地转化为 SQL。如果您可能在数据库上进行联合,那么您最好:

list1.Concat(list2).GroupBy(item => item.ItemParentId).Select(grp => grp.First())

获取两个列表(尚未过滤掉重复项),然后按 ItemParentId 对它们进行分组,然后从每个组中获取第一个元素,因此给出等效的结果。

这也适用于 linq-to-objects,但使用相等比较器的版本会更快。

【讨论】:

  • 那个 HashCode 是干什么用的?目前我必须通过两个属性进行比较,但 HashCode 只返回一个,这可能是我没有得到我想要的东西的问题吗?
  • 哈希码用于维护已处理的基于哈希的元素集,以便可以识别和丢弃重复项。为什么要通过两个属性进行比较,您的问题是您只想通过一个来区分(ItemParentId)?
  • 是的,但它适用于单,现在我不得不添加一个属性,ItemChildId,并做同样的事情,只使所有项目的联合,其中 ItemChildId && ItemParentId 不相等,所以在方法Equal我可以做,但是HashCode怎么样,重要吗?
  • 如果您不更改GetHashCode,它会起作用,但不是最佳的(GetHashCode() 必须为我们认为相等的对象返回相等的结果,但是应该为不同的对象返回尽可能多的不同结果,以获得更有效的基于哈希的存储)。尝试return unchecked(ItemChildId * 31 + ItemParentId); 一个应该足够合理的。
【解决方案3】:

也许这会有所帮助:

        var list1 = new List<Item>
        {
            new Item { Id = 1, Name = "item1", ItemParentId = 100 },
            new Item { Id = 2, Name = "item2", ItemParentId = 200 },
            new Item { Id = 3, Name = "item3", ItemParentId = 300 },
            new Item { Id = 1, Name = "item4", ItemParentId = 400 }
        };

        var list2 = new List<Item>
        {
            new Item { Id = 0, Name = "item5", ItemParentId = 500 },
            new Item { Id = 0, Name = "item6", ItemParentId = 300 },
            new Item { Id = 0, Name = "item7", ItemParentId = 400 },
        };

        var listMerge = list1.Union(list2.Where(l2 => !list1.Select(l1 => l1.ItemParentId).Contains(l2.ItemParentId))).ToList();

我个人会把这个表达分成两部分:

        var list2new = list2.Where(l2 => !list1.Select(l1 => l1.ItemParentId).Contains(l2.ItemParentId));
        var listMerge = list1.Union(list2new).ToList();

【讨论】:

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