65.9K
CodeProject 正在变化。 阅读更多。
Home

比较同一类的两个复杂或原始对象(替代方案)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.43/5 (4投票s)

2015年11月3日

CPOL

2分钟阅读

viewsIcon

39246

这是“比较同一类的两个复杂对象”的替代方案。

引言

比较同一类的两个复杂对象[^]

这是对所引用技巧的一种替代。标题暗示此方法将对复杂类进行相等性测试,但如果复杂类包含 datetime 属性,则它总是会返回 false。

我的版本也只有一个方法出口点,并且在复杂类中遇到第一个不相等的属性时,也会停止比较。除此之外,我对其进行了重构,只有当要比较的对象不是原始类型、字符串或 datetime(这些都需要特殊处理)时,才会进入复杂类处理代码。

可能还有其他类型需要特殊处理,但将发现和实现这些类型作为程序员的练习。

最后,如果您想使用类似的方法,您可能还需要考虑检查非公共属性以及公共属性。为此,只需取消注释 BindingFlags.NonPublic 枚举器即可。

使用代码

public static bool CompareEquals<T>(this T objectFromCompare, T objectToCompare)
{
    bool result = (objectFromCompare == null && objectToCompare == null);
    PropertyInfo property;

    if (!result)
    {	
        try
        {
            Type fromType = objectFromCompare.GetType();
            if (fromType.IsPrimitive)
            {
                result = objectFromCompare.Equals(objectToCompare);
            }
            
            else if (fromType.FullName.Contains("System.String"))
            {
                result = ((objectFromCompare as string) == (objectToCompare as string));
            }
            
            else if (fromType.FullName.Contains("DateTime"))
            {
                result = (DateTime.Parse(objectFromCompare.ToString()).Ticks == DateTime.Parse(objectToCompare.ToString()).Ticks);
            }
            
            // stringbuilder handling here is optional, but doing it this way cuts down
            // on reursive calls to this method
            else if (fromType.FullName.Contains("System.Text.StringBuilder"))
            {
                result = ((objectFromCompare as StringBuilder).ToString() == (objectToCompare as StringBuilder).ToString());
            }
            
			else if (fromType.FullName.Contains("System.Collections.Generic.Dictionary"))
			{
				
				PropertyInfo countProp  = fromType.GetProperty("Count");
				PropertyInfo keysProp   = fromType.GetProperty("Keys");
				PropertyInfo valuesProp = fromType.GetProperty("Values");
				int fromCount = (int)countProp.GetValue(objectFromCompare, null);
				int toCount   =  (int)countProp.GetValue(objectToCompare, null);

				result = (fromCount == toCount);
				if (result && fromCount > 0)
				{
					var fromKeys = keysProp.GetValue(objectFromCompare, null);
					var toKeys = keysProp.GetValue(objectToCompare, null);
					result = CompareEquals(fromKeys, toKeys);
					if (result)
					{
						var fromValues = valuesProp.GetValue(objectFromCompare, null);
						var toValues = valuesProp.GetValue(objectToCompare, null);
						result = CompareEquals(fromValues, toValues);
					}
				}
			}

            // collections presented a unique problem in that the original code always returned
            // false when they're encountered. The following code was tested with generic
            // lists (of both primitive types and complex classes). I see no reason why an
            // ObservableCollection shouldn't also work here (unless the properties or
            // methods already used are not appropriate).
            else if (fromType.IsGenericType || fromType.IsArray)
            {
                string propName = (fromType.IsGenericType) ? "Count" : "Length";
                string methName = (fromType.IsGenericType) ? "get_Item" : "Get";
                PropertyInfo propInfo = fromType.GetProperty(propName);
                MethodInfo methInfo = fromType.GetMethod(methName);
                if (propInfo != null && methInfo != null)
                {
                    int fromCount = (int)propInfo.GetValue(objectFromCompare, null); 
                    int toCount = (int)propInfo.GetValue(objectToCompare, null); 
                    result = (fromCount == toCount);
                    if (result && fromCount > 0)
                    {
                        for (int index = 0; index < fromCount; index++) 
                        { 
                            // Get an instance of the item in the list object 
                            object fromItem = methInfo.Invoke(objectFromCompare, new object[] { index });
                            object toItem = methInfo.Invoke(objectToCompare, new object[] { index });
                            result = CompareEquals(fromItem, toItem);
                            if (!result)
                            {
                                break;
                            }
                        }
                    }
                }
                else
                {
                }
            }
            else
            {
                PropertyInfo[] props = fromType.GetProperties(BindingFlags.Public | BindingFlags.Instance );
                foreach (PropertyInfo prop in props)
                {
                    property = prop;
                    Type type = fromType.GetProperty(prop.Name).GetValue(objectToCompare, null).GetType();
                    object dataFromCompare = fromType.GetProperty(prop.Name).GetValue(objectFromCompare, null);
                    object dataToCompare = fromType.GetProperty(prop.Name).GetValue(objectToCompare, null);
                    result = CompareEquals(Convert.ChangeType(dataFromCompare, type), Convert.ChangeType(dataToCompare, type));
                    // no point in continuing beyond the first property that isn't equal.
                    if (!result)
                    {
                        break;
                    }
                }
            }
        }
        catch (Exception ex)
        {
        }
    }
    return result;

}    

用法:给定以下示例类...

public class AbcClass
{
    public StringBuilder Text { get; set; }
    public double Value { get; set; }

    public AbcClass()
    {
        this.Text = new StringBuilder("text");
        this.Value = 100d;
    }
}

public class XyzClass
{
    public string Str { get; set; }
    public int X { get; set; }
    public DateTime Date { get; set; }
    public Int64 Long { get; set; }
    public AbcClass ABC { get; set; }
    public List<int> IntList { get; set; }
    public int[] IntArray { get; set; }
    public List<AbcClass> AbcList { get; set; }
    public AbcClass[] AbcArray{ get; set; }
    public Dictionary<int, string> Dict {  get; set; }

    public XyzClass()
    {
        this.Str = "test";
        this.X = 7;
        this.Date = new DateTime(2015, 10, 4, 0, 0, 0);
        this.Long = 1234567890;
        this.ABC = new AbcClass();
        this.IntList = new List<int>(){1,2,3};
        this.IntArray = new int[]{4,5,6};
        this.AbcList = new List<AbcClass>(){ new AbcClass(), new AbcClass()};
        this.AbcArray = new AbcClass[]{new AbcClass()};
        this.Dict = new Dictionary<int,string>(){ {1,"One"}, {2, "Two"} };
    }
}

以下代码说明了用法。

int x = 5;
int y = 5;
XyzClass a = new XyzClass();
XyzClass b = new XyzClass();

// Both of these should return true
bool equals = x.CompareEquals(y);
equals = a.CompareEquals(b);

y=10;
b.Date = DateTime.Now;
b.Str="tester";

// both of these should return false
equals = x.CompareEquals(y);
// this one should return false after finding the first non-equal property
equals = a.CompareEquals(b);

便捷的重载(2015 年 11 月 10 日更新)

我正在编写一些代码,并使用此方法比较两个 FileSystemInfo 对象。我意识到我可以通过简单地比较我感兴趣的属性(LastWriteTimeUtc)并忽略其余属性来节省时间。因此,我为该方法创建了几个重载。

第一个比较单个属性(被比较的两个对象仍然必须是相同类型。如果属性不存在,或者属性存在但两个对象中的值不相等,则结果为 false。否则为 true。

public static bool CompareEquals<T>(this T objectFromCompare, T objectToCompare, string propertyName)
{
    bool result = (objectFromCompare == null && objectToCompare == null);
    if (!result)
    {
        try
        {
            Type fromType = objectFromCompare.GetType();
            PropertyInfo prop = fromType.GetProperty(propertyName);
            if (prop != null)
            {
                Type type = prop.GetValue(objectToCompare).GetType();
                object dataFromCompare = prop.GetValue(objectFromCompare, null);
                object dataToCompare = prop.GetValue(objectToCompare, null);
                result = CompareEquals(Convert.ChangeType(dataFromCompare, type), Convert.ChangeType(dataToCompare, type));
            }
        }
        catch (Exception ex)
        {
        }
    }
    return result;
}

第二个重载比较一个属性数组。所有指定的属性都必须存在并且相等,此方法才能返回 true。

public static bool CompareEquals<T>(this T objectFromCompare, T objectToCompare, string[] propertyNames)
{
    bool result = (objectFromCompare == null && objectToCompare == null);
    if (!result)
    {
        try
        {
            foreach (string propertyName in propertyNames)
            {
                result = CompareEquals(objectFromCompare, objectToCompare, propertyNames);
                if (!result)
                {
                    break;
                }
            }
        }
        catch (Exception ex)
        {
        }
    }
    return result;
}

关注点

我怀疑您会发现其他对象可能会强制对此方法进行调整。如果是这样,请在下面的评论部分中注明。

您应该意识到耗尽堆栈空间的可能性,因为此方法支持嵌套的复杂类,并且根据大小和嵌套深度,您可能会遇到内存问题。仅作为友好的警告...

历史

  • 2015 年 11 月 10 日 - 更新,包括允许比较特定属性的重载。
  • 2015 年 11 月 5 日 - 更新方法,包括对 Dictionaries 的支持。
  • 2015 年 11 月 4 日 - 更新方法,包括对 StringBuilder 和集合的支持,以及修复对嵌入式复杂类的支持。
  • 2015 年 11 月 3 日 - 原始发布
© . All rights reserved.