干净的方法检查两个对象之间的所有属性(两个除外)是否匹配? [重复]

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了干净的方法检查两个对象之间的所有属性(两个除外)是否匹配? [重复]相关的知识,希望对你有一定的参考价值。

这个问题在这里已有答案:

我有一个包含大约20个属性的组件的数据库。要确定是否需要更新,我想检查两个对象的所有属性(DateCreated和Id除外)是否匹配。如果所有匹配都没有更新,如果没有,则更新db。

Component comp_InApp = new Component()
{
    Id = null,
    Description = "Commponent",
    Price = 100,
    DateCreated = "2019-01-30",
    // Twenty more prop
};

Component comp_InDb = new Component()
{
    Id = 1,
    Description = "Component",
    Price = 100,
    DateCreated = "2019-01-01",
    // Twenty more prop
};

// Check if all properties match, except DateCreated and Id.
if (comp_InApp.Description == comp_InDb.Description &&
    comp_InApp.Price == comp_InDb.Price
    // Twenty more prop
    )
{
    // Everything up to date.
}
else
{
    // Update db.
}

这是有效的,但它不是一个非常干净的方式有20个属性。有更好的方法以更清洁的方式获得相同的结果吗?

答案

当我不想/没有时间写自己的DeepEqualEquals方法时,我正在使用GetHashCode

您只需从NuGet安装它:

Install-Package DeepEqual

并使用它像:

    if (comp_InApp.IsDeepEqual(comp_InDb))
    {
        // Everything up to date.
    }
    else
    {
        // Update db.
    }

但请记住,这只适用于您希望显式比较对象的情况,但不适用于当您要调用ListEquals时要从GetHashCode或类似情况中移除对象的情况。

另一答案

一种方法是,创建一个实现IEqualityComparer<Component>的类来封装这个逻辑,并避免你修改类Comparer本身(如果你不是一直想要这个Equals逻辑)。然后你可以将它用于两个Equals实例的简单Component,甚至可以用于接受它作为附加参数的所有LINQ methods

class ComponentComparer : IEqualityComparer<Component>
{
    public bool Equals(Component x, Component y)
    {
        if (object.ReferenceEquals(x, y)) return true;
        if (x == null || y == null) return false;
        return x.Price == y.Price && x.Description == y.Description;
    }

    public int GetHashCode(Component obj)
    {
        unchecked 
        {
            int hash = 17;
            hash = hash * 23 + obj.Price.GetHashCode();
            hash = hash * 23 + obj.Description?.GetHashCode() ?? 0;
            // ...
            return hash;
        }
    }
}

你的简单用例:

var comparer = new ComponentComparer();
bool equal = comparer.Equals(comp_InApp, comp_InDb);

如果您有两个集合并想知道其中的差异,它也可以工作,例如:

IEnumerable<Component> missingInDb = inAppList.Except( inDbList, comparer );
另一答案

这是一个反射解决方案:

    static bool AreTwoEqual(Component inApp, Component inDb)
    {
        string[] propertiesToExclude = new string[] { "DateCreated", "Id" };

        PropertyInfo[] propertyInfos = typeof(Component).GetProperties()
                                                 .Where(x => !propertiesToExclude.Contains(x.Name))
                                                 .ToArray();

        foreach (PropertyInfo propertyInfo in propertyInfos)
        {
            bool areSame = inApp.GetType().GetProperty(propertyInfo.Name).GetValue(inApp, null).Equals(inDb.GetType().GetProperty(propertyInfo.Name).GetValue(inDb, null));

            if (!areSame)
            {
                return false;
            }
        }

        return true;
    }

另一答案

您可以使用Reflection但它可能会降低您的应用程序速度。创建该比较器的另一种方法是使用Linq表达式生成它。试试这段代码:

public static Expression<Func<T, T, bool>> CreateAreEqualExpression<T>(params string[] toExclude)
{
    var type = typeof(T);
    var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
        .Where(p => !toExclude.Contains(p.Name))
        .ToArray();

    var p1 = Expression.Parameter(type, "p1");
    var p2 = Expression.Parameter(type, "p2");

    Expression body = null;
    foreach (var property in props)
    {
        var pare = Expression.Equal(
            Expression.PropertyOrField(p1, property.Name),
            Expression.PropertyOrField(p2, property.Name)
        );

        body = body == null ? pare : Expression.AndAlso(body, pare);
    }

    if (body == null) // all properties are excluded
        body = Expression.Constant(true);

    var lambda = Expression.Lambda<Func<T, T, bool>>(body, p1, p2);
    return lambda;
}

它会生成一个看起来像的表达式

(Component p1, Component p2) => ((p1.Description == p2.Description) && (p1.Price == p2.Price))

用法很简单

var comporator = CreateAreEqualExpression<Component>("Id", "DateCreated")
    .Compile(); // save compiled comparator somewhere to use it again later
var areEqual = comporator(comp_InApp, comp_InDb);

编辑:为了使其更安全,您可以使用lambdas排除属性

public static Expression<Func<T, T, bool>> CreateAreEqualExpression<T>(
  params Expression<Func<T, object>>[] toExclude)
{
    var exclude = toExclude
        .Select(e =>
        {
            // for properties that is value types (int, DateTime and so on)
            var name = ((e.Body as UnaryExpression)?.Operand as MemberExpression)?.Member.Name;
            if (name != null)
                return name;

            // for properties that is reference type
            return (e.Body as MemberExpression)?.Member.Name;
        })
        .Where(n => n != null)
        .Distinct()            
        .ToArray();

    var type = typeof(T);
    var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
        .Where(p => !exclude.Contains(p.Name))
        .ToArray();

    /* rest of code is unchanged */
}

现在使用它时我们有一个IntelliSense支持:

var comparator = CreateAreEqualExpression<Component>(
        c => c.Id,
        c => c.DateCreated)
    .Compile();

以上是关于干净的方法检查两个对象之间的所有属性(两个除外)是否匹配? [重复]的主要内容,如果未能解决你的问题,请参考以下文章

Vue/JS - 检查两个对象的所有值是不是为空

如何在 Python 中比较两个字符串(英语除外)之间的相似性

使用 OpenCV abs() 稳定帧差分,不重叠区域除外

.net core 3.0 Signalr - 01 基础篇

C#用反射实现两个类的对象之间相同属性的值的复制

如何检查两个日期之间的差异(以秒为单位)?