我有两个相同类型的复杂(即具有string,int,double,List和其他自制数据类型的对象)对象。我想比较它们的内容,以确保它们是相同的。注意:该对象没有实现.Equals(我无法控制)并且没有实现IComparable。
是否有通用方法(反射?)来比较两个对象的内容?
谢谢!
答案 0 :(得分:14)
我创建了一个类来执行.NET Objects的深度比较。参见:
答案 1 :(得分:6)
我的工作解决方案。!
private bool Compare(object obj1, object obj2)
{
if (obj1 == null || obj2 == null)
{
return false;
}
if (!obj1.GetType().Equals(obj2.GetType()))
{
return false;
}
Type type = obj1.GetType();
if (type.IsPrimitive || typeof(string).Equals(type))
{
return obj1.Equals(obj2);
}
if (type.IsArray)
{
Array first = obj1 as Array;
Array second = obj2 as Array;
var en = first.GetEnumerator();
int i = 0;
while (en.MoveNext())
{
if (!Compare(en.Current, second.GetValue(i)))
return false;
i++;
}
}
else
{
foreach (PropertyInfo pi in type.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public))
{
var val = pi.GetValue(obj1);
var tval = pi.GetValue(obj2);
if (!Compare(val, tval))
return false;
}
foreach (FieldInfo fi in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public))
{
var val = fi.GetValue(obj1);
var tval = fi.GetValue(obj2);
if (!Compare(val, tval))
return false;
}
}
return true;
}
希望它有所帮助。!
答案 2 :(得分:3)
是否有通用的方法来比较两个对象的内容?
是的,但通常称为IComparable接口。
如果您可以从课程中下载并创建一个实现IComparable的子项,那可能是理想的。
答案 3 :(得分:3)
反思就是这样,但问题是包含的类型 - 例如,您不能只使用Equals
或EqualityComparer<T>
,因为子数据也如果它是List<T>
等,将无法方便地比较。
您多久需要这样做?你能序列化它们并比较序列化值吗?这可能是最强大的选择。
答案 4 :(得分:3)
GetHashcode适合我。
我在所有公共相关属性X-OR-ed的类中重写GetHashcode() e.g。
override GetHashCode()
{
return A.GetHashCode() ^ B.GetHashCode ^ C.SafeString().Get..
}
我遍历所有类,再次X-OR值。 IsModified将先前的HashValue与当前值进行比较。 两个不同的对象可能确实返回相同的HashValue,可能有1到40亿,但出于很多目的,这对我来说已经足够了。
但是我有一个更好的想法,使用MemoryStream
这是一个扩展名:
public static bool IsBinaryEqualTo(this object obj, object obj1)
{
using (MemoryStream memStream = new MemoryStream())
{
if (obj == null || obj1 == null)
{
if (obj == null && obj1 == null)
return true;
else
return false;
}
BinaryFormatter binaryFormatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));
binaryFormatter.Serialize(memStream, obj);
byte[] b1 = memStream.ToArray();
memStream.SetLength(0);
binaryFormatter.Serialize(memStream, obj1);
byte[] b2 = memStream.ToArray();
if (b1.Length != b2.Length)
return false;
for (int i = 0; i < b1.Length; i++)
{
if (b1[i] != b2[i])
return false;
}
return true;
}
}
答案 5 :(得分:3)
我刚写了我的版本。此函数使用泛型和反射。它的工作原理是自我递归,直到对象中的所有东西都已经比较或找到一个不相等的东西。
public class Utils
{
public bool CompareObjects<T>(T expectInput, T actualInput)
{
// If T is primitive type.
if (typeof(T).IsPrimitive)
{
if (expectInput.Equals(actualInput))
{
return true;
}
return false;
}
if (expectInput is IEquatable<T>)
{
if (expectInput.Equals(actualInput))
{
return true;
}
return false;
}
if (expectInput is IComparable)
{
if (((IComparable)expectInput).CompareTo(actualInput) == 0)
{
return true;
}
return false;
}
// If T is implement IEnumerable.
if (expectInput is IEnumerable)
{
var expectEnumerator = ((IEnumerable)expectInput).GetEnumerator();
var actualEnumerator = ((IEnumerable)actualInput).GetEnumerator();
var canGetExpectMember = expectEnumerator.MoveNext();
var canGetActualMember = actualEnumerator.MoveNext();
while (canGetExpectMember && canGetActualMember && true)
{
var currentType = expectEnumerator.Current.GetType();
object isEqual = typeof(Utils).GetMethod("CompareObjects").MakeGenericMethod(currentType).Invoke(null, new object[] { expectEnumerator.Current, actualEnumerator.Current });
if ((bool)isEqual == false)
{
return false;
}
canGetExpectMember = expectEnumerator.MoveNext();
canGetActualMember = actualEnumerator.MoveNext();
}
if (canGetExpectMember != canGetActualMember)
{
return false;
}
return true;
}
// If T is class.
var properties = typeof(T).GetProperties();
foreach (var property in properties)
{
var expectValue = typeof(T).GetProperty(property.Name).GetValue(expectInput);
var actualValue = typeof(T).GetProperty(property.Name).GetValue(actualInput);
if (expectValue == null || actualValue == null)
{
if (expectValue == null && actualValue == null)
{
continue;
}
return false;
}
object isEqual = typeof(Utils).GetMethod("CompareObjects").MakeGenericMethod(property.PropertyType).Invoke(null, new object[] { expectValue, actualValue });
if ((bool)isEqual == false)
{
return false;
}
}
return true;
}
}
答案 6 :(得分:2)
将对象序列化为XML字符串,您最终可以对序列化的2个对象进行字符串比较...
private string Serialize<T>(T value)
{
if (value == null)
{
return string.Empty;
}
try
{
XmlSerializer xmlserializer = new XmlSerializer(typeof(T));
StringWriter stringWriter = new StringWriter();
XmlWriter writer = XmlWriter.Create(stringWriter);
xmlserializer.Serialize(writer, value);
string serializeXml = stringWriter.ToString();
writer.Close();
return serializeXml;
}
catch (Exception ex)
{
return string.Empty;
}
}
}
答案 7 :(得分:2)
对于我的上一个项目,我对一些功能做了很好的深入研究。
public class ObjektHelper
{
/// <summary>
/// Compairs two Objects and gives back true if they are equal
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj1">Object 1</param>
/// <param name="obj2">Object 2</param>
/// <param name="consideredFieldNames">If the list is not mepty, only the field within equal names are compaired.</param>
/// <param name="notConsideredFieldNames">If you want not compair some fields enter their name in this list.</param>
/// <returns></returns>
public static bool DeepCompare<T>(T obj1, T obj2, string[] consideredFieldNames, params string[] notConsideredFieldNames)
{
var errorList = new List<object>();
if (notConsideredFieldNames == null) notConsideredFieldNames = new[] {""};
DeepCompare(obj1, obj2, errorList, consideredFieldNames, notConsideredFieldNames, false);
return errorList.Count <= 0;
}
/// <summary>
/// Compairs two Objects and gives an error list back.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj1"></param>
/// <param name="obj2"></param>
/// <param name="errorList">The error list gives back the names of the fields that are not equal.</param>
/// <param name="consideredFieldNames">If the list is not mepty, only the field within equal names are compaired.</param>
/// <param name="notConsideredFieldNames">If you want not compair some fields enter their name in this list.</param>
/// <param name="endWithErrors">If the value is false, the method end at the first error.</param>
public static void DeepCompare<T>(T obj1, T obj2, List<object> errorList, string[] consideredFieldNames, string[] notConsideredFieldNames, bool endWithErrors)
{
if (errorList == null) throw new Exception("errorListliste ist NULL");
if (Equals(obj1, default(T)) && Equals(obj2, default(T))) return;
if (Equals(obj1, default(T)) || Equals(obj2, default(T)))
{
errorList.Add("One of the object are null!");
return;
}
if (!endWithErrors && errorList != null && errorList.Count > 0) throw new Exception("ErrorListliste is not empty");
var type1 = obj1.GetType();
var type2 = obj2.GetType();
var propertyInfos1 = type1.GetProperties();
var propertyInfos2 = type2.GetProperties();
// To use the access via index, the list have to be ordered!
var propertyInfoOrdered1 = propertyInfos1.OrderBy(p => p.Name).ToArray();
var propertyInfoOrdered2 = propertyInfos2.OrderBy(p => p.Name).ToArray();
if (type1 != type2) errorList.AddRange(new List<object> {type1, type2});
else
{
for (var i = 0; i < propertyInfos1.Length; i++)
{
var t1 = propertyInfoOrdered1[i].PropertyType;
var t2 = propertyInfoOrdered2[i].PropertyType;
if (t1 != t2)
{
errorList.AddRange(new List<object> {type1, type2});
continue;
}
var name1 = propertyInfoOrdered1[i].Name;
var name2 = propertyInfoOrdered2[i].Name;
// Use the next 4 lines to find a bug
//if (name1 == "Enter name of field with the bug")
// Console.WriteLine(name1);
//if (name2 == "Enter name of field1 with the bug" || name2 == "Enter name of field2 with the bug")
// Console.WriteLine(name2);
if (consideredFieldNames != null && !consideredFieldNames.Contains(name1)) continue;
if (notConsideredFieldNames != null && notConsideredFieldNames.Contains(name1)) continue;
var value1 = propertyInfoOrdered1[i].GetValue(obj1, null);
var value2 = propertyInfoOrdered2[i].GetValue(obj2, null);
// check Attributes
var guiName1 = (propertyInfoOrdered1[i].GetCustomAttributes().FirstOrDefault(a => a.GetType() == typeof(GuiNameofModelAttribute)) as GuiNameofModelAttribute)?.GuiName;
var guiName2 = (propertyInfoOrdered2[i].GetCustomAttributes().FirstOrDefault(a => a.GetType() == typeof(GuiNameofModelAttribute)) as GuiNameofModelAttribute)?.GuiName;
// create errorListrange
var temperrorListRange = new List<object>();
if (guiName1 != null && guiName2 != null) temperrorListRange.AddRange(new List<object> { guiName1, guiName2 });
else temperrorListRange.AddRange(new List<object> { propertyInfoOrdered1[i], propertyInfoOrdered2[i] });
// both fields are null = OK
if ((value1 == null && value2 == null) || (value1 is Guid && value2 is Guid)) continue;
// one of the fields is null = errorList
if (value1 == null || value2 == null) errorList.AddRange(temperrorListRange);
// Value types, Enum and String compair
else if (t1.BaseType == typeof (ValueType) || t1.BaseType == typeof (Enum) || t1 == typeof (string))
{
if (!value1.Equals(value2)) errorList.AddRange(temperrorListRange);
}
// List, array, generic lists, collection and bindinglist compair
else if (t1 == typeof (Array) || (t1.IsGenericType && (t1.GetGenericTypeDefinition() == typeof (List<>) ||
t1.GetGenericTypeDefinition() == typeof (IList<>) ||
t1.GetGenericTypeDefinition() == typeof (Collection<>) ||
t1.GetGenericTypeDefinition() == typeof (ICollection<>) ||
t1.GetGenericTypeDefinition() == typeof (ObservableCollection<>) ||
t1.GetGenericTypeDefinition() == typeof (BindingList<>) ||
t1.GetGenericTypeDefinition() == typeof (BindingList<>)
)))
DeepListCompare(value1 as IEnumerable<object>, value2 as IEnumerable<object>, errorList, consideredFieldNames, notConsideredFieldNames, endWithErrors);
// Clas compair
else if (t1.IsClass || t1.IsAnsiClass) DeepCompare(value1, value2, errorList, consideredFieldNames, notConsideredFieldNames, endWithErrors);
else throw new NotImplementedException();
if (!endWithErrors && errorList.Count > 0) break;
}
}
} // End DeepCompare<T>
/// <summary>
/// Compairs two lists and gives back true if they are equal.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tlist1">Generic list 1</param>
/// <param name="tlist2">Generic List 2</param>
/// <returns></returns>
public static bool DeepListCompare<T>(T tlist1, T tlist2)
{
var errorList = new List<object>();
DeepCompare(tlist1, tlist2, errorList, null, null, false);
return errorList.Count <= 0;
}
/// <summary>
/// Compairs two lists and gives backthe error list.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tlist1">Generic list 1</param>
/// <param name="tlist2">Generic list 2</param>
/// <param name="errorList">The error list gives back the names of the fields that are not equal.</param>
/// <param name="consideredFieldNames">If the list is not mepty, only the field within equal names are compaired.</param>
/// <param name="notConsideredFieldNames">If you want not compair some fields enter their name in this list.</param>
/// <param name="endWithErrors">If the value is false, the method end at the first error.</param>
public static void DeepListCompare<T>(T tlist1, T tlist2, List<object> errorList, string[] consideredFieldNames, string[] notConsideredFieldNames, bool endWithErrors)
where T : IEnumerable<object>
{
if (errorList == null) throw new Exception("errorListliste ist NULL");
if (!endWithErrors && errorList.Count > 0) throw new Exception("errorListliste ist nicht Leer");
if (Equals(tlist1, null) || Equals(tlist2, null))
{
errorList.AddRange(new List<object> {tlist1, tlist2});
return;
}
var type1 = tlist1.GetType();
var type2 = tlist2.GetType();
var propertyInfos1 = type1.GetProperties();
var propertyInfos2 = type2.GetProperties();
// To use the access via index, the list have to be ordered!
var propertyInfoOrdered1 = propertyInfos1.OrderBy(p => p.Name).ToArray();
var propertyInfoOrdered2 = propertyInfos2.OrderBy(p => p.Name).ToArray();
for (var i = 0; i < propertyInfos1.Length; i++)
{
var t1 = propertyInfoOrdered1[i].PropertyType;
var t2 = propertyInfoOrdered2[i].PropertyType;
if (t1 != t2) errorList.AddRange(new List<object> {t1, t2});
else
{
// Kick out index
if (propertyInfoOrdered1[i].GetIndexParameters().Length != 0)
{
continue;
}
// Get value
var value1 = propertyInfoOrdered1[i].GetValue(tlist1, null) as IEnumerable<object>;
var value2 = propertyInfoOrdered2[i].GetValue(tlist2, null) as IEnumerable<object>;
if (value1 == null || value2 == null) continue;
// Only run through real lists.
if (t1 == typeof (Array) ||
t1.IsGenericType && t1.GetGenericTypeDefinition() == typeof (List<>) ||
t1.IsGenericType && t1.GetGenericTypeDefinition() == typeof (Collection<>))
{
// cast
var objectList1 = value1.ToList();
var objectList2 = value2.ToList();
if (objectList1.Count == 0 && objectList1.Count == 0)
{
//errorList.AddRange(new List<Object> { objectList1, objectList1 });
continue;
}
foreach (var item1 in objectList1)
{
foreach (var item2 in objectList2)
{
var temperrorListCount = errorList.Count;
DeepCompare(item1, item2, errorList, consideredFieldNames, notConsideredFieldNames, endWithErrors);
if (temperrorListCount != errorList.Count) continue;
objectList2.Remove(item2);
break;
}
if (!endWithErrors && errorList.Count > 0) break;
}
}
else DeepCompare(value1, value2, errorList, consideredFieldNames, notConsideredFieldNames, endWithErrors);
}
if (!endWithErrors && errorList.Count > 0) break;
}
} // End DeepListCompare<T>
} // end class ObjectHelper
[AttributeUsage(AttributeTargets.All)]
public class GuiNameofModelAttribute : Attribute
{
public readonly string GuiName;
public GuiNameofModelAttribute(string guiName)
{
GuiName = guiName;
}
}
答案 8 :(得分:1)
您可以在另一个类中编写实用程序方法来进行比较。但是,假设所讨论的类的属性是公开可访问的。是吗?
答案 9 :(得分:1)
感谢MemoryStream方法,Marc。当我看到&#34;这个&#34;我确定有一个错误。在参数中,但令人惊讶的是编译器实际上让你这样做,对吧?我稍作改动,选择覆盖Equals()代替。 Kudos也使用长度和数组比较而不是SequenceEquals()。需要花费额外的时间来写,但根据http://www.dotnetperls.com/sequenceequal,性能要好得多。
public override bool Equals(object obj)
{
// If comparison object is null or is a different type, no further comparisons are necessary...
if (obj == null || GetType() != obj.GetType())
{
return false;
}
// Compare objects using byte arrays...
using (MemoryStream memStream = new MemoryStream())
{
BinaryFormatter binaryFormatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));
// Get byte array of "this" object...
binaryFormatter.Serialize(memStream, this);
byte[] b1 = memStream.ToArray();
// Get byte array of object to be compared...
memStream.SetLength(0);
binaryFormatter.Serialize(memStream, obj);
byte[] b2 = memStream.ToArray();
// Compare array sizes. If equal, no further comparisons are necessary...
if (b1.Length != b2.Length)
return false;
// If sizes are equal, compare each byte while inequality is not found...
for (int i = 0; i < b1.Length; i++)
{
if (b1[i] != b2[i])
return false;
}
}
return true;
}
答案 10 :(得分:1)
我发现最快,最简单的方法是使用MessagePack序列化两个对象,然后比较字节数组。
public static bool DeepEquals(object o1, object o2)
{
var b1 = MessagePackSerializer.Serialize(o1, ContractlessStandardResolver.Instance);
var b2 = MessagePackSerializer.Serialize(o2, ContractlessStandardResolver.Instance);
return b1.SequenceEqual(b2);
}
答案 11 :(得分:0)
好吧,你可以编写一些逻辑来比较两个对象的所有属性。当它是具有复杂子类型的对象图时,这会变得复杂,因此您需要确定接近的程度。
答案 12 :(得分:0)
你需要一种别的比较方法;在C ++中你可以编写一个全局函数,但我不认为c#允许这样做,就像Java没有那样。
我要做的是写一个实现iComparable的类,并且有一个ctor,它接受你想要的类的对象,并包含你的Equals函数。设置它所以它保留的是对原始对象的引用,以保存mallocations 然后你可以写
Foo(A).Equals(new Foo(B))
您可以改为继承所提供的类,但这意味着需要创建和跟踪这些内容。