ฉันมีวัตถุสองชนิดที่เหมือนกันและฉันต้องการวนลูปผ่านคุณสมบัติสาธารณะในแต่ละรายการและแจ้งเตือนผู้ใช้เกี่ยวกับคุณสมบัติที่ไม่ตรงกัน
เป็นไปได้ไหมที่จะทำสิ่งนี้โดยไม่ทราบว่าคุณสมบัติของวัตถุมีอะไรบ้าง?
ฉันมีวัตถุสองชนิดที่เหมือนกันและฉันต้องการวนลูปผ่านคุณสมบัติสาธารณะในแต่ละรายการและแจ้งเตือนผู้ใช้เกี่ยวกับคุณสมบัติที่ไม่ตรงกัน
เป็นไปได้ไหมที่จะทำสิ่งนี้โดยไม่ทราบว่าคุณสมบัติของวัตถุมีอะไรบ้าง?
คำตอบ:
ใช่มีการสะท้อน - สมมติว่าคุณสมบัติแต่ละประเภทใช้Equalsอย่างเหมาะสม อีกทางเลือกหนึ่งคือใช้ReflectiveEqualsแบบเรียกซ้ำสำหรับทุกคนยกเว้นบางประเภทที่รู้จัก
public bool ReflectiveEquals(object first, object second)
{
if (first == null && second == null)
{
return true;
}
if (first == null || second == null)
{
return false;
}
Type firstType = first.GetType();
if (second.GetType() != firstType)
{
return false; // Or throw an exception
}
// This will only use public properties. Is that enough?
foreach (PropertyInfo propertyInfo in firstType.GetProperties())
{
if (propertyInfo.CanRead)
{
object firstValue = propertyInfo.GetValue(first, null);
object secondValue = propertyInfo.GetValue(second, null);
if (!object.Equals(firstValue, secondValue))
{
return false;
}
}
}
return true;
}
แน่นอนคุณสามารถสะท้อน นี่คือรหัสที่จะดึงคุณสมบัติออกจากประเภทที่กำหนด
var info = typeof(SomeType).GetProperties();
หากคุณสามารถให้ข้อมูลเพิ่มเติมเกี่ยวกับสิ่งที่คุณกำลังเปรียบเทียบเกี่ยวกับคุณสมบัติเราสามารถรวมอัลกอริทึมการกระจายแบบพื้นฐานได้ รหัสสำหรับอินสแตนซ์นี้จะแตกต่างกันตามชื่อ
public bool AreDifferent(Type t1, Type t2) {
var list1 = t1.GetProperties().OrderBy(x => x.Name).Select(x => x.Name);
var list2 = t2.GetProperties().OrderBy(x => x.Name).Select(x => x.Name);
return list1.SequenceEqual(list2);
}
.Select(...)
ฉันรู้ว่านี่อาจเป็น overkill แต่นี่คือคลาส ObjectComparer ของฉันที่ฉันใช้เพื่อจุดประสงค์นี้:
/// <summary>
/// Utility class for comparing objects.
/// </summary>
public static class ObjectComparer
{
/// <summary>
/// Compares the public properties of any 2 objects and determines if the properties of each
/// all contain the same value.
/// <para>
/// In cases where object1 and object2 are of different Types (both being derived from Type T)
/// we will cast both objects down to the base Type T to ensure the property comparison is only
/// completed on COMMON properties.
/// (ex. Type T is Foo, object1 is GoodFoo and object2 is BadFoo -- both being inherited from Foo --
/// both objects will be cast to Foo for comparison)
/// </para>
/// </summary>
/// <typeparam name="T">Any class with public properties.</typeparam>
/// <param name="object1">Object to compare to object2.</param>
/// <param name="object2">Object to compare to object1.</param>
/// <param name="propertyInfoList">A List of <see cref="PropertyInfo"/> objects that contain data on the properties
/// from object1 that are not equal to the corresponding properties of object2.</param>
/// <returns>A boolean value indicating whether or not the properties of each object match.</returns>
public static bool GetDifferentProperties<T> ( T object1 , T object2 , out List<PropertyInfo> propertyInfoList )
where T : class
{
return GetDifferentProperties<T>( object1 , object2 , null , out propertyInfoList );
}
/// <summary>
/// Compares the public properties of any 2 objects and determines if the properties of each
/// all contain the same value.
/// <para>
/// In cases where object1 and object2 are of different Types (both being derived from Type T)
/// we will cast both objects down to the base Type T to ensure the property comparison is only
/// completed on COMMON properties.
/// (ex. Type T is Foo, object1 is GoodFoo and object2 is BadFoo -- both being inherited from Foo --
/// both objects will be cast to Foo for comparison)
/// </para>
/// </summary>
/// <typeparam name="T">Any class with public properties.</typeparam>
/// <param name="object1">Object to compare to object2.</param>
/// <param name="object2">Object to compare to object1.</param>
/// <param name="ignoredProperties">A list of <see cref="PropertyInfo"/> objects
/// to ignore when completing the comparison.</param>
/// <param name="propertyInfoList">A List of <see cref="PropertyInfo"/> objects that contain data on the properties
/// from object1 that are not equal to the corresponding properties of object2.</param>
/// <returns>A boolean value indicating whether or not the properties of each object match.</returns>
public static bool GetDifferentProperties<T> ( T object1 , T object2 , List<PropertyInfo> ignoredProperties , out List<PropertyInfo> propertyInfoList )
where T : class
{
propertyInfoList = new List<PropertyInfo>();
// If either object is null, we can't compare anything
if ( object1 == null || object2 == null )
{
return false;
}
Type object1Type = object1.GetType();
Type object2Type = object2.GetType();
// In cases where object1 and object2 are of different Types (both being derived from Type T)
// we will cast both objects down to the base Type T to ensure the property comparison is only
// completed on COMMON properties.
// (ex. Type T is Foo, object1 is GoodFoo and object2 is BadFoo -- both being inherited from Foo --
// both objects will be cast to Foo for comparison)
if ( object1Type != object2Type )
{
object1Type = typeof ( T );
object2Type = typeof ( T );
}
// Remove any properties to be ignored
List<PropertyInfo> comparisonProps =
RemoveProperties( object1Type.GetProperties() , ignoredProperties );
foreach ( PropertyInfo object1Prop in comparisonProps )
{
Type propertyType = null;
object object1PropValue = null;
object object2PropValue = null;
// Rule out an attempt to check against a property which requires
// an index, such as one accessed via this[]
if ( object1Prop.GetIndexParameters().GetLength( 0 ) == 0 )
{
// Get the value of each property
object1PropValue = object1Prop.GetValue( object1 , null );
object2PropValue = object2Type.GetProperty( object1Prop.Name ).GetValue( object2 , null );
// As we are comparing 2 objects of the same type we know
// that they both have the same properties, so grab the
// first non-null value
if ( object1PropValue != null )
propertyType = object1PropValue.GetType().GetInterface( "IComparable" );
if ( propertyType == null )
if ( object2PropValue != null )
propertyType = object2PropValue.GetType().GetInterface( "IComparable" );
}
// If both objects have null values or were indexed properties, don't continue
if ( propertyType != null )
{
// If one property value is null and the other is not null,
// they aren't equal; this is done here as a native CompareTo
// won't work with a null value as the target
if ( object1PropValue == null || object2PropValue == null )
{
propertyInfoList.Add( object1Prop );
}
else
{
// Use the native CompareTo method
MethodInfo nativeCompare = propertyType.GetMethod( "CompareTo" );
// Sanity Check:
// If we don't have a native CompareTo OR both values are null, we can't compare;
// hence, we can't confirm the values differ... just go to the next property
if ( nativeCompare != null )
{
// Return the native CompareTo result
bool equal = ( 0 == (int) ( nativeCompare.Invoke( object1PropValue , new object[] {object2PropValue} ) ) );
if ( !equal )
{
propertyInfoList.Add( object1Prop );
}
}
}
}
}
return propertyInfoList.Count == 0;
}
/// <summary>
/// Compares the public properties of any 2 objects and determines if the properties of each
/// all contain the same value.
/// <para>
/// In cases where object1 and object2 are of different Types (both being derived from Type T)
/// we will cast both objects down to the base Type T to ensure the property comparison is only
/// completed on COMMON properties.
/// (ex. Type T is Foo, object1 is GoodFoo and object2 is BadFoo -- both being inherited from Foo --
/// both objects will be cast to Foo for comparison)
/// </para>
/// </summary>
/// <typeparam name="T">Any class with public properties.</typeparam>
/// <param name="object1">Object to compare to object2.</param>
/// <param name="object2">Object to compare to object1.</param>
/// <returns>A boolean value indicating whether or not the properties of each object match.</returns>
public static bool HasSamePropertyValues<T> ( T object1 , T object2 )
where T : class
{
return HasSamePropertyValues<T>( object1 , object2 , null );
}
/// <summary>
/// Compares the public properties of any 2 objects and determines if the properties of each
/// all contain the same value.
/// <para>
/// In cases where object1 and object2 are of different Types (both being derived from Type T)
/// we will cast both objects down to the base Type T to ensure the property comparison is only
/// completed on COMMON properties.
/// (ex. Type T is Foo, object1 is GoodFoo and object2 is BadFoo -- both being inherited from Foo --
/// both objects will be cast to Foo for comparison)
/// </para>
/// </summary>
/// <typeparam name="T">Any class with public properties.</typeparam>
/// <param name="object1">Object to compare to object2.</param>
/// <param name="object2">Object to compare to object1.</param>
/// <param name="ignoredProperties">A list of <see cref="PropertyInfo"/> objects
/// to ignore when completing the comparison.</param>
/// <returns>A boolean value indicating whether or not the properties of each object match.</returns>
public static bool HasSamePropertyValues<T> ( T object1 , T object2 , List<PropertyInfo> ignoredProperties )
where T : class
{
// If either object is null, we can't compare anything
if ( object1 == null || object2 == null )
{
return false;
}
Type object1Type = object1.GetType();
Type object2Type = object2.GetType();
// In cases where object1 and object2 are of different Types (both being derived from Type T)
// we will cast both objects down to the base Type T to ensure the property comparison is only
// completed on COMMON properties.
// (ex. Type T is Foo, object1 is GoodFoo and object2 is BadFoo -- both being inherited from Foo --
// both objects will be cast to Foo for comparison)
if ( object1Type != object2Type )
{
object1Type = typeof ( T );
object2Type = typeof ( T );
}
// Remove any properties to be ignored
List<PropertyInfo> comparisonProps =
RemoveProperties( object1Type.GetProperties() , ignoredProperties );
foreach ( PropertyInfo object1Prop in comparisonProps )
{
Type propertyType = null;
object object1PropValue = null;
object object2PropValue = null;
// Rule out an attempt to check against a property which requires
// an index, such as one accessed via this[]
if ( object1Prop.GetIndexParameters().GetLength( 0 ) == 0 )
{
// Get the value of each property
object1PropValue = object1Prop.GetValue( object1 , null );
object2PropValue = object2Type.GetProperty( object1Prop.Name ).GetValue( object2 , null );
// As we are comparing 2 objects of the same type we know
// that they both have the same properties, so grab the
// first non-null value
if ( object1PropValue != null )
propertyType = object1PropValue.GetType().GetInterface( "IComparable" );
if ( propertyType == null )
if ( object2PropValue != null )
propertyType = object2PropValue.GetType().GetInterface( "IComparable" );
}
// If both objects have null values or were indexed properties, don't continue
if ( propertyType != null )
{
// If one property value is null and the other is not null,
// they aren't equal; this is done here as a native CompareTo
// won't work with a null value as the target
if ( object1PropValue == null || object2PropValue == null )
{
return false;
}
// Use the native CompareTo method
MethodInfo nativeCompare = propertyType.GetMethod( "CompareTo" );
// Sanity Check:
// If we don't have a native CompareTo OR both values are null, we can't compare;
// hence, we can't confirm the values differ... just go to the next property
if ( nativeCompare != null )
{
// Return the native CompareTo result
bool equal = ( 0 == (int) ( nativeCompare.Invoke( object1PropValue , new object[] {object2PropValue} ) ) );
if ( !equal )
{
return false;
}
}
}
}
return true;
}
/// <summary>
/// Removes any <see cref="PropertyInfo"/> object in the supplied List of
/// properties from the supplied Array of properties.
/// </summary>
/// <param name="allProperties">Array containing master list of
/// <see cref="PropertyInfo"/> objects.</param>
/// <param name="propertiesToRemove">List of <see cref="PropertyInfo"/> objects to
/// remove from the supplied array of properties.</param>
/// <returns>A List of <see cref="PropertyInfo"/> objects.</returns>
private static List<PropertyInfo> RemoveProperties (
IEnumerable<PropertyInfo> allProperties , IEnumerable<PropertyInfo> propertiesToRemove )
{
List<PropertyInfo> innerPropertyList = new List<PropertyInfo>();
// Add all properties to a list for easy manipulation
foreach ( PropertyInfo prop in allProperties )
{
innerPropertyList.Add( prop );
}
// Sanity check
if ( propertiesToRemove != null )
{
// Iterate through the properties to ignore and remove them from the list of
// all properties, if they exist
foreach ( PropertyInfo ignoredProp in propertiesToRemove )
{
if ( innerPropertyList.Contains( ignoredProp ) )
{
innerPropertyList.Remove( ignoredProp );
}
}
}
return innerPropertyList;
}
}
ปัญหาที่แท้จริง: วิธีการได้รับความแตกต่างของสองชุด?
วิธีที่เร็วที่สุดที่ฉันพบคือการแปลงชุดเป็นพจนานุกรมก่อนจากนั้นจึงแตกต่าง นี่เป็นวิธีการทั่วไป:
static IEnumerable<T> DictionaryDiff<K, T>(Dictionary<K, T> d1, Dictionary<K, T> d2)
{
return from x in d1 where !d2.ContainsKey(x.Key) select x.Value;
}
จากนั้นคุณสามารถทำสิ่งนี้:
static public IEnumerable<PropertyInfo> PropertyDiff(Type t1, Type t2)
{
var d1 = t1.GetProperties().ToDictionary(x => x.Name);
var d2 = t2.GetProperties().ToDictionary(x => x.Name);
return DictionaryDiff(d1, d2);
}
ใช่. ใช้การสะท้อน ด้วย Reflection คุณสามารถทำสิ่งต่าง ๆ เช่น:
//given object of some type
object myObjectFromSomewhere;
Type myObjOriginalType = myObjectFromSomewhere.GetType();
PropertyInfo[] myProps = myObjOriginalType.GetProperties();
จากนั้นคุณสามารถใช้คลาส PropertyInfo ที่ได้เพื่อเปรียบเทียบทุกสิ่ง
เปรียบเทียบสองวัตถุประเภทเดียวกันโดยใช้ LINQ และ Reflection NB! นี่คือการเขียนซ้ำของโซลูชันจาก Jon Skeet แต่ด้วยรูปแบบที่กะทัดรัดและทันสมัยยิ่งขึ้น นอกจากนี้ควรสร้าง IL ที่มีประสิทธิภาพมากกว่าเล็กน้อย
มันเป็นอะไรแบบนี้
public bool ReflectiveEquals(LocalHdTicket serverTicket, LocalHdTicket localTicket)
{
if (serverTicket == null && localTicket == null) return true;
if (serverTicket == null || localTicket == null) return false;
var firstType = serverTicket.GetType();
// Handle type mismatch anyway you please:
if(localTicket.GetType() != firstType) throw new Exception("Trying to compare two different object types!");
return !(from propertyInfo in firstType.GetProperties()
where propertyInfo.CanRead
let serverValue = propertyInfo.GetValue(serverTicket, null)
let localValue = propertyInfo.GetValue(localTicket, null)
where !Equals(serverValue, localValue)
select serverValue).Any();
}
where !Equals(serverValue, localValue)ด้วยfirstType.IsValueType ? !Equals(serverValue, localValue) : !ReflectiveEquals(serverValue, localValue)
Type.GetPropertiesจะแสดงรายการคุณสมบัติแต่ละอย่างของประเภทที่กำหนด จากนั้นใช้PropertyInfo.GetValueเพื่อตรวจสอบค่า
ดังที่หลายคนกล่าวถึงวิธีเรียกซ้ำนี่คือฟังก์ชันที่คุณสามารถส่งผ่านชื่อการค้นหาและคุณสมบัติเพื่อเริ่มต้นด้วย:
public static void loopAttributes(PropertyInfo prop, string targetAttribute, object tempObject)
{
foreach (PropertyInfo nestedProp in prop.PropertyType.GetProperties())
{
if(nestedProp.Name == targetAttribute)
{
//found the matching attribute
}
loopAttributes(nestedProp, targetAttribute, prop.GetValue(tempObject);
}
}
//in the main function
foreach (PropertyInfo prop in rootObject.GetType().GetProperties())
{
loopAttributes(prop, targetAttribute, rootObject);
}