【问题标题】:C# Find difference of two custom object lists based only on some propertiesC#仅根据某些属性查找两个自定义对象列表的差异
【发布时间】:2017-11-17 04:36:18
【问题描述】:

所以我有以下对象:

public class user
{
    // navigation properties
    public DateTime lastRefresh { get; set; }
    public userParent parent { get; set; }

    // query data
    public string id { get; set; }
    public DateTime created_at { get; set; }
    public int plan { get; set; }
}

所以我正在处理这些对象的两个列表,并希望获得第三个列表,它是两个集合的差异,如下所示:

List<user> freshUsers;
List<user> oldUsers;

//code populate lists

//get difference
List<user> uniqueUsers = freshUsers.Except(oldUsers).ToList();

但是,freshUsers 中的用户没有稍后手动添加的导航属性(lastRefresh 和 parent),即使有,lastRefresh 也不会匹配 oldUsers 中的相同用户。

所以我在这里需要的是一种仅根据其数据属性(id、created_at、plan)来获取列表之间差异的方法,并且想知道最好的方法。我应该创建一个自定义相等来执行它,还是有一些巧妙的方法使用 LINQ 仅比较合适的 3 个值?

【问题讨论】:

  • “freshUsers 中的用户没有导航属性”是什么意思?两个列表属于同一类型。某种类型的某些对象不可能与同一类型的其他对象不具有相同的属性,是吗?
  • 另外,您编写的示例仅提供了不在旧用户中的 fresh 用户,而不是任何不在新用户中的旧用户。那是你要的吗? (你的问题陈述说你想要差异,这将包括后一组)
  • stackoverflow.com/questions/15540891/…,尤其是一些扩展方法。
  • @RufusL 通过“freshUsers 中的用户没有导航属性”你是正确的,通过“没有”我只是说他们是空的/没有设置所以我不期望他们匹配任何东西。而且你说我没有得到真正的区别也是正确的,除了我想要的功能(获得所有新用户但确保没有重叠)。
  • @NetMage 谢谢,这是一组非常有用的答案

标签: c# list linq collections


【解决方案1】:

在您的用户类中,实现接口IEquatable&lt;user&gt;。这创建了一种方法来将一个用户对象与另一个用户对象进行比较。在此方法中,您需要定义两个对象何时相同(通过比较属性)。

或者...创建另一个具有比较两个用户的任务的类。你需要实现IEqualityComparer&lt;user&gt;。实现这个接口将提供一个方法来比较两个用户对象。现在您可以调用Except,并将此比较器的实例作为第二个参数。

【讨论】:

    【解决方案2】:

    可能最简单的方法是覆盖Equals 方法(以及GetHashCode)。然后你可以使用System.Linq 扩展方法来得到你想要的:

    public class user
    {
        // navigation properties
        public DateTime lastRefresh { get; set; }
        public userParent parent { get; set; }
    
        // query data
        public string id { get; set; }
        public DateTime created_at { get; set; }
        public int plan { get; set; }
    
        public override bool Equals(object obj)
        {
            var other = obj as user;
            return Equals(other);
        }
    
        protected bool Equals(user other)
        {
            if (other == null) return false;
            return string.Equals(id, other.id) && 
                created_at.Equals(other.created_at) && 
                plan == other.plan;
        }
    
        public override int GetHashCode()
        {
            unchecked
            {
                var hashCode = id?.GetHashCode() ?? 0;
                hashCode = (hashCode * 397) ^ created_at.GetHashCode();
                hashCode = (hashCode * 397) ^ plan;
                return hashCode;
            }
        }
    }
    

    现在您可以使用您的Linq 代码获得您想要的内容,并且将使用您的Equals 覆盖代码比较这些项目:

    //get difference
    List<user> uniqueUsers = freshUsers.Except(oldUsers).ToList();
    

    【讨论】:

    • 一般来说,当覆盖EqualsGetHashCode时,类值也应该是只读的,以免破坏依赖哈希码永不改变的字典和哈希表。
    • @Enigmativity 我没有想到这一点。但是当Equals 在许多情况下使用非只读字段时,我很困惑它是如何工作的。你会为GetHashCode 使用更少的字段吗?在这种情况下,idcreated_at 可能是只读的(因此我猜是通过构造函数设置的)。你会怎么做?
    • 你可以让GetHashCode为每个值返回0,它会起作用——但在哈希表中效率会降低。所以,是的,您可以只使用那些不可变的字段,但最好将它们全部设为不可变。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-26
    相关资源
    最近更新 更多