简体   繁体   English

比较具有相同接口的2个对象的属性

[英]Comparing the properties of 2 objects with the same interface

I have a method where I compare the properties of 2 classes that share the same interface. 我有一个比较两个共享相同接口的类的属性的方法。 There are a number of properties involved and rather than check each one, I would rather iterate through them instead, saving repetitive coding. 其中涉及许多属性,而不是检查每个属性,而是要遍历它们,以节省重复的编码。 I thought it might be possible to do this with reflection, but I can't work out how. 我以为可以通过反射来做到这一点,但我不知道如何做到。 This code failed as I did not understand what I was doing. 这段代码失败了,因为我不明白自己在做什么。

    public void IsEqual(IPropertyComparer src, IPropertyComparer property)
    {
        PropertyInfo[] srcPI = src.GetType().GetPublicProperties();
        PropertyInfo[] propertyPI = property.GetType().GetPublicProperties();
        var numberOfProperties = srcPI.Count();
        for (var i = 0; i < numberOfProperties; i++)
        {
            var srcValue = srcPI[i].GetValue(src, null);
            var propertyValue = propertyPI[i].GetValue(property, null);
            var propertyName = srcPI[i].Name;
            if(srcValue.Equals(propertyValue) == false)
            {
                this.Action = "ValidateExistingPropertyFailOn" + propertyName;
                this.Data.Error = true;
                this.Data.ErrorMessage =
                    string.Format(this.Template, this.UPRN, propertyName, srcValue, propertyValue);
                return;
            }
        }

There is an extension method in the code. 代码中有一个扩展方法。 The extension method fails as the Type is not recognised as an interface; 扩展方法失败,因为无法将类型识别为接口;

public static PropertyInfo[] GetPublicProperties(this Type type)
{
    if (type.IsInterface)
    {
        var propertyInfos = new List<PropertyInfo>();

        var considered = new List<Type>();
        var queue = new Queue<Type>();
        considered.Add(type);
        queue.Enqueue(type);
        while (queue.Count > 0)
        {
            var subType = queue.Dequeue();
            foreach (var subInterface in subType.GetInterfaces())
            {
                if (considered.Contains(subInterface))
                {
                    continue;
                }
                considered.Add(subInterface);
                queue.Enqueue(subInterface);
            }

            var typeProperties = subType.GetProperties(
                BindingFlags.FlattenHierarchy
                | BindingFlags.Public
                | BindingFlags.Instance);

            var newPropertyInfos = typeProperties
                .Where(x => !propertyInfos.Contains(x));

            propertyInfos.InsertRange(0, newPropertyInfos);
        }

        return propertyInfos.ToArray();
    }

    return type.GetProperties(BindingFlags.FlattenHierarchy
        | BindingFlags.Public | BindingFlags.Instance);
}

If I understand correctly, you're trying compare two objects that implement an interface in common, and to this comparison only the properties defined in the interface will be use to compare. 如果我理解正确,那么您正在尝试比较两个共同实现一个接口的对象,并且对此比较,仅使用该接口中定义的属性进行比较。

If this is the case, try this: 如果是这种情况,请尝试以下操作:

EDITED 已编辑

To prevent null exception I edited the code: 为了防止空异常,我编辑了代码:

bool IsEqual(IPropertyComparer o1, IPropertyComparer o2)
{
    var props = typeof(IPropertyComparer).GetProperties();

    foreach(var prop in props)
    {
        var v1 = prop.GetValue(o1);
        var v2 = prop.GetValue(o2);

        if(v1 == null)
        {
            if(v2 != null) return false;
        }
        else
        {
            if(!v1.Equals(v2)) return false;
        }
    }

    return true;
}

If your needs for comparing two instances of objects grow more complicated, you should check out this package on NuGet: 如果比较对象的两个实例的需求变得更加复杂,则应在NuGet上查看以下软件包:

https://www.nuget.org/packages/CompareNETObjects/ https://www.nuget.org/packages/CompareNETObjects/

And codeplex page: 和codeplex页面:

http://comparenetobjects.codeplex.com/ http://comparenetobjects.codeplex.com/

Its very good at giving you a "difference", which can some times be what you are really looking for... 它非常擅长为您提供“差异”,有时候这可能正是您真正想要的...

UPDATE - A Generic, reusable way to check for Equality, based on fields within an object. UPDATE-一种基于对象内字段的通用可重用方法,用于检查是否相等。

I stumbled upon the ValueObject<T> class written by Jimmy Bogard which might interest some folk. 我偶然发现了吉米·博加德(Jimmy Bogard)编写的ValueObject<T>类,该类可能会让某些人感兴趣。 Its original purpose is well suited to DDD where objects of equal properties are considered equal, regardless of their reference type based hash code. 它的最初目的非常适合于DDD,在DDD中,具有相同属性的对象被视为相等,而不管其基于引用类型的哈希码如何。

http://grabbagoft.blogspot.com/2007/06/generic-value-object-equality.html http://grabbagoft.blogspot.com/2007/06/generic-value-object-equality.html

Here is a slightly updated version using Linq to improve readability and a better way to process precondition checks - eg x.ForNull(nameof(firstInCompare)); 这是一个使用Linq进行了稍微更新的版本,以提高可读性,并且是一种更好的处理前提条件检查的方法-例如x.ForNull(nameof(firstInCompare));

public abstract class ValueObject<T> : IEquatable<T> where T : ValueObject<T>
{
    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;

        var other = obj as T;

        return Equals(other);
    }

    public override int GetHashCode()
    {
        var fields = GetFields();

        const int startValue = 17;
        const int multiplier = 59;

        return fields
            .Select(field => field.GetValue(this))
            .Where(value => value != null)
            .Aggregate(startValue, (current, value) => current * multiplier + value.GetHashCode());
    }

    public virtual bool Equals(T other)
    {
        if (other == null)
            return false;

        var t = GetType();
        var otherType = other.GetType();

        if (t != otherType)
            return false;

        var fields = t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

        foreach (var field in fields)
        {
            var value1 = field.GetValue(other);
            var value2 = field.GetValue(this);

            if (value1 == null)
            {
                if (value2 != null)
                    return false;
            }
            else if (!value1.Equals(value2))
                return false;
        }

        return true;
    }

    private IEnumerable<FieldInfo> GetFields()
    {
        var t = GetType();

        t.ForNull("this");

        var fields = new List<FieldInfo>();

        while (t != typeof(object))
        {
            if (t == null) continue;
            fields.AddRange(t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));

            t = t.BaseType;
        }

        return fields;
    }

    public static bool operator ==(ValueObject<T> firstInCompare, ValueObject<T> secondInCompare)
    {
        firstInCompare.ForNull(nameof(firstInCompare));
        secondInCompare.ForNull(nameof(secondInCompare));

        return firstInCompare?.Equals(secondInCompare) ?? false;
    }

    public static bool operator !=(ValueObject<T> firstInCompare, ValueObject<T> secondInCompare)
    {
        return !(firstInCompare == secondInCompare);
    }
}

The precondition guards can be found in a static helper class like this 可以在这样的静态帮助器类中找到前提条件防护

public static class Guard
{
    public static void ForLessEqualZero(this int value, string parameterName)
    {
        if (value <= 0)
        {
            throw new ArgumentOutOfRangeException(parameterName);
        }
    }

    public static void ForPrecedesDate(this DateTime value, DateTime dateToPrecede, string parameterName)
    {
        if (value >= dateToPrecede)
        {
            throw new ArgumentOutOfRangeException(parameterName);
        }
    }

    public static void ForNullOrEmpty(this string value, string parameterName)
    {
        if (string.IsNullOrEmpty(value))
        {
            throw new ArgumentOutOfRangeException(parameterName);
        }
    }

    public static void ForNull<T>(this T value, string parameterName)
    {
        ForValueType<T>(parameterName);

        if (value == null)
        {
            throw new ArgumentNullException(parameterName);
        }
    }

    private static void ForValueType<T>(string parameterName)
    {
        if (typeof(T).IsValueType)
        {
            throw new ArgumentException("parameter should be reference type, not value type", parameterName);
        }
    }
}

To check for equality in a generic, reusable way you could wrap your objects in the ValueObject<T> . 要以通用,可重用的方式检查是否相等,可以将对象包装在ValueObject<T>

Or you could steal the implementation and do it in a more specific way. 或者,您可以窃取实现并以更特定的方式来实现。

I've been looking for something like this some time ago, and finally managed to write this function. 一段时间前,我一直在寻找类似的东西,最后设法编写了此函数。 It should work for your interface too. 它也应该适用于您的界面。

bool IsEqual(object obj)
    {
        var type = this.GetType();
        bool SameObj = true;
        //for each public property from your class
        type.GetProperties().ToList().ForEach(prop =>
        {
            //dynamically checks there equals
            if (!prop.GetValue(this, null).Equals(prop.GetValue(obj, null)))
            {
                SameObj = false;
            }
        });
        return SameObj;
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM