比较同一类的两个复杂或原始对象(替代方案)
这是“比较同一类的两个复杂对象”的替代方案。
引言
这是对所引用技巧的一种替代。标题暗示此方法将对复杂类进行相等性测试,但如果复杂类包含 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 日 - 原始发布