Comparando las propiedades del objeto en c #

Esto es lo que se me ocurrió como método en una clase heredada por muchas de mis otras clases. La idea es que permite la comparación simple entre propiedades de Objetos del mismo Tipo.

Ahora, esto funciona, pero con el interés de mejorar la calidad de mi código, pensé que lo descartaría para un análisis minucioso. ¿Cómo puede ser mejor / más eficiente / etc.?

///  /// Compare property values (as strings) ///  ///  ///  public bool PropertiesEqual(object comparisonObject) { Type sourceType = this.GetType(); Type destinationType = comparisonObject.GetType(); if (sourceType == destinationType) { PropertyInfo[] sourceProperties = sourceType.GetProperties(); foreach (PropertyInfo pi in sourceProperties) { if ((sourceType.GetProperty(pi.Name).GetValue(this, null) == null && destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null) == null)) { // if both are null, don't try to compare (throws exception) } else if (!(sourceType.GetProperty(pi.Name).GetValue(this, null).ToString() == destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null).ToString())) { // only need one property to be different to fail Equals. return false; } } } else { throw new ArgumentException("Comparison object must be of the same type.","comparisonObject"); } return true; } 

Estaba buscando un fragmento de código que hiciera algo similar para ayudar con la prueba de unidad de escritura. Esto es lo que terminé usando.

 public static bool PublicInstancePropertiesEqual(T self, T to, params string[] ignore) where T : class { if (self != null && to != null) { Type type = typeof(T); List ignoreList = new List(ignore); foreach (System.Reflection.PropertyInfo pi in type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)) { if (!ignoreList.Contains(pi.Name)) { object selfValue = type.GetProperty(pi.Name).GetValue(self, null); object toValue = type.GetProperty(pi.Name).GetValue(to, null); if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue))) { return false; } } } return true; } return self == to; } 

EDITAR:

Mismo código que el anterior, pero utiliza los métodos LINQ y de extensión:

 public static bool PublicInstancePropertiesEqual(this T self, T to, params string[] ignore) where T : class { if (self != null && to != null) { var type = typeof(T); var ignoreList = new List(ignore); var unequalProperties = from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance) where !ignoreList.Contains(pi.Name) && pi.GetUnderlyingType().IsSimpleType() && pi.GetIndexParameters().Length == 0 let selfValue = type.GetProperty(pi.Name).GetValue(self, null) let toValue = type.GetProperty(pi.Name).GetValue(to, null) where selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)) select selfValue; return !unequalProperties.Any(); } return self == to; } public static class TypeExtensions { ///  /// Determine whether a type is simple (String, Decimal, DateTime, etc) /// or complex (ie custom class with public properties and methods). ///  ///  public static bool IsSimpleType( this Type type) { return type.IsValueType || type.IsPrimitive || new[] { typeof(String), typeof(Decimal), typeof(DateTime), typeof(DateTimeOffset), typeof(TimeSpan), typeof(Guid) }.Contains(type) || (Convert.GetTypeCode(type) != TypeCode.Object); } public static Type GetUnderlyingType(this MemberInfo member) { switch (member.MemberType) { case MemberTypes.Event: return ((EventInfo)member).EventHandlerType; case MemberTypes.Field: return ((FieldInfo)member).FieldType; case MemberTypes.Method: return ((MethodInfo)member).ReturnType; case MemberTypes.Property: return ((PropertyInfo)member).PropertyType; default: throw new ArgumentException ( "Input MemberInfo must be if type EventInfo, FieldInfo, MethodInfo, or PropertyInfo" ); } } } 

ACTUALIZACIÓN: la última versión de Compare-Net-Objects se encuentra en GitHub , tiene paquete NuGet y Tutorial . Se puede llamar como

 //This is the comparison class CompareLogic compareLogic = new CompareLogic(); ComparisonResult result = compareLogic.Compare(person1, person2); //These will be different, write out the differences if (!result.AreEqual) Console.WriteLine(result.DifferencesString); 

O si necesita cambiar alguna configuración, use

 CompareLogic basicComparison = new CompareLogic() { Config = new ComparisonConfig() { MaxDifferences = propertyCount //add other configurations } }; 

La lista completa de parámetros configurables se encuentra en ComparisonConfig.cs

Respuesta original:

Las limitaciones que veo en tu código:

  • El más grande es que no hace una comparación profunda de objetos.

  • No hace una comparación elemento por elemento en caso de que las propiedades sean listas o contengan listas como elementos (esto puede ir en n niveles).

  • No se tiene en cuenta que no se deben comparar algunos tipos de propiedades (por ejemplo, una propiedad de Func utilizada para fines de filtrado, como la de la clase PagedCollectionView).

  • No realiza un seguimiento de las propiedades realmente diferentes (para que pueda mostrar en sus afirmaciones).

Estuve buscando hoy una solución para propósitos de pruebas unitarias para hacer una propiedad por comparación profunda de la propiedad y terminé usando: http://comparenetobjects.codeplex.com .

Es una biblioteca gratuita con solo una clase que puedes usar de esta manera:

 var compareObjects = new CompareObjects() { CompareChildren = true, //this turns deep compare one, otherwise it's shallow CompareFields = false, CompareReadOnly = true, ComparePrivateFields = false, ComparePrivateProperties = false, CompareProperties = true, MaxDifferences = 1, ElementsToIgnore = new List() { "Filter" } }; Assert.IsTrue( compareObjects.Compare(objectA, objectB), compareObjects.DifferencesString ); 

Además, se puede volver a comstackr fácilmente para Silverlight. Simplemente copie la clase en un proyecto de Silverlight y elimine una o dos líneas de código para realizar comparaciones que no están disponibles en Silverlight, como la comparación de miembros privados.

Si el rendimiento no importa, puede serializarlos y comparar los resultados:

 var serializer = new XmlSerializer(typeof(TheObjectType)); StringWriter serialized1 = new StringWriter(), serialized2 = new StringWriter(); serializer.Serialize(serialized1, obj1); serializer.Serialize(serialized2, obj2); bool areEqual = serialized1.ToString() == serialized2.ToString(); 

Creo que sería mejor seguir el patrón para Anular Objeto # Igual ()
Para una mejor descripción: lea el C # eficaz de Bill Wagner, artículo 9, creo

 public override Equals(object obOther) { if (null == obOther) return false; if (object.ReferenceEquals(this, obOther) return true; if (this.GetType() != obOther.GetType()) return false; # private method to compare members. return CompareMembers(this, obOther as ThisClass); } 
  • También en métodos que verifican la igualdad, debe devolver verdadero o falso. o son iguales o no lo son … en lugar de lanzar una excepción, devuelve falso.
  • Consideraría anular Object # Iguals.
  • Aunque debe haber considerado esto, usar Reflection para comparar propiedades es supuestamente lento (no tengo números para respaldar esto). Este es el comportamiento predeterminado para valueType # Equals en C # y se recomienda que anule Equals para los tipos de valor y haga una comparación inteligente para el rendimiento. (Anteriormente, leí esto rápidamente ya que tienes una colección de objetos de propiedad personalizados … es malo).

Actualización-diciembre de 2011:

  • Por supuesto, si el tipo ya tiene una producción Igual (), entonces necesita otro enfoque.
  • Si está utilizando esto para comparar estructuras de datos inmutables exclusivamente para fines de prueba, no debe agregar Igual a las clases de producción (Alguien podría manipular las pruebas al encauzar la implementación Equals o puede evitar la creación de una implementación Equals requerida por la producción) .

Creo que la respuesta de Big T fue bastante buena, pero faltaba la comparación profunda, así que la modifiqué un poco:

 using System.Collections.Generic; using System.Reflection; /// Comparison class. public static class Compare { /// Compare the public instance properties. Uses deep comparison. /// The reference object. /// The object to compare. /// Ignore property with name. /// Type of objects. /// True if both objects are equal, else false. public static bool PublicInstancePropertiesEqual(T self, T to, params string[] ignore) where T : class { if (self != null && to != null) { var type = self.GetType(); var ignoreList = new List(ignore); foreach (var pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (ignoreList.Contains(pi.Name)) { continue; } var selfValue = type.GetProperty(pi.Name).GetValue(self, null); var toValue = type.GetProperty(pi.Name).GetValue(to, null); if (pi.PropertyType.IsClass && !pi.PropertyType.Module.ScopeName.Equals("CommonLanguageRuntimeLibrary")) { // Check of "CommonLanguageRuntimeLibrary" is needed because string is also a class if (PublicInstancePropertiesEqual(selfValue, toValue, ignore)) { continue; } return false; } if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue))) { return false; } } return true; } return self == to; } } 

Agregaría la siguiente línea al método PublicInstancePropertiesEqual para evitar los errores de copiar y pegar:

 Assert.AreNotSame(self, to); 

¿Sobreescribes .ToString () en todos tus objetos que están en las propiedades? De lo contrario, esa segunda comparación podría volver con nulo.

Además, en esa segunda comparación, estoy al margen sobre la construcción de! (A == B) en comparación con (A! = B), en términos de legibilidad, seis meses / dos años a partir de ahora. La línea en sí es bastante amplia, lo cual está bien si tienes un monitor amplio, pero es posible que no se imprima muy bien. (nitpick)

¿Todos sus objetos siempre usan propiedades tales que este código funcionará? ¿Podría haber algunos datos internos, no propietarios que podrían ser diferentes de un objeto a otro, pero todos los datos expuestos son los mismos? Estoy pensando en algunos datos que podrían cambiar con el tiempo, como dos generadores de números aleatorios que coinciden con el mismo número en un punto, pero que van a producir dos secuencias de información diferentes, o simplemente cualquier información que no se exponga a través de la interfaz de propiedad.

Si solo compara objetos del mismo tipo o más abajo en la cadena de herencia, ¿por qué no especifica el parámetro como su tipo de base, en lugar de como objeto?

También haga comprobaciones nulas en el parámetro también.

Además, utilizaría ‘var’ solo para hacer que el código sea más legible (si es código c # 3)

Además, si el objeto tiene tipos de referencia como propiedades, entonces simplemente está llamando a ToString () en ellos, lo que realmente no compara los valores. Si no se sobrepasa ToString, simplemente devolverá el nombre del tipo como una cadena que podría devolver falsos positivos.

Lo primero que sugeriría sería dividir la comparación real para que sea un poco más legible (también saqué el ToString () – ¿es necesario?):

 else { object originalProperty = sourceType.GetProperty(pi.Name).GetValue(this, null); object comparisonProperty = destinationType.GetProperty(pi.Name).GetValue(comparisonObject, null); if (originalProperty != comparisonProperty) return false; 

La siguiente sugerencia sería minimizar el uso de la reflexión tanto como sea posible, es realmente lento. Quiero decir, realmente lento. Si va a hacer esto, le sugiero que guarde en caché las referencias de la propiedad. No estoy íntimamente familiarizado con Reflection API, así que si está un poco apagado, simplemente ajústelo para que se compile:

 // elsewhere Dictionary lookupDictionary = new Dictionary; Property[] objectProperties = null; if (lookupDictionary.ContainsKey(sourceType)) { objectProperties = lookupProperties[sourceType]; } else { // build array of Property references PropertyInfo[] sourcePropertyInfos = sourceType.GetProperties(); Property[] sourceProperties = new Property[sourcePropertyInfos.length]; for (int i=0; i < sourcePropertyInfos.length; i++) { sourceProperties[i] = sourceType.GetProperty(pi.Name); } // add to cache objectProperties = sourceProperties; lookupDictionary[object] = sourceProperties; } // loop through and compare against the instances 

Sin embargo, debo decir que estoy de acuerdo con los otros carteles. Esto huele perezoso e ineficiente. Deberías implementar IComparable en su lugar :-).

aquí se revisó uno para tratar null = null como igual

  private bool PublicInstancePropertiesEqual(T self, T to, params string[] ignore) where T : class { if (self != null && to != null) { Type type = typeof(T); List ignoreList = new List(ignore); foreach (PropertyInfo pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (!ignoreList.Contains(pi.Name)) { object selfValue = type.GetProperty(pi.Name).GetValue(self, null); object toValue = type.GetProperty(pi.Name).GetValue(to, null); if (selfValue != null) { if (!selfValue.Equals(toValue)) return false; } else if (toValue != null) return false; } } return true; } return self == to; } 

Terminé haciendo esto:

  public static string ToStringNullSafe(this object obj) { return obj != null ? obj.ToString() : String.Empty; } public static bool Compare(T a, T b) { int count = a.GetType().GetProperties().Count(); string aa, bb; for (int i = 0; i < count; i++) { aa = a.GetType().GetProperties()[i].GetValue(a, null).ToStringNullSafe(); bb = b.GetType().GetProperties()[i].GetValue(b, null).ToStringNullSafe(); if (aa != bb) { return false; } } return true; } 

Uso:

  if (Compare(a, b)) 

Actualizar

Si quieres ignorar algunas propiedades por nombre:

  public static string ToStringNullSafe(this object obj) { return obj != null ? obj.ToString() : String.Empty; } public static bool Compare(T a, T b, params string[] ignore) { int count = a.GetType().GetProperties().Count(); string aa, bb; for (int i = 0; i < count; i++) { aa = a.GetType().GetProperties()[i].GetValue(a, null).ToStringNullSafe(); bb = b.GetType().GetProperties()[i].GetValue(b, null).ToStringNullSafe(); if (aa != bb && ignore.Where(x => x == a.GetType().GetProperties()[i].Name).Count() == 0) { return false; } } return true; } 

Uso:

  if (MyFunction.Compare(a, b, "Id","AnotherProp")) 

Puede optimizar su código llamando a GetProperties solo una vez por tipo:

 public static string ToStringNullSafe(this object obj) { return obj != null ? obj.ToString() : String.Empty; } public static bool Compare(T a, T b, params string[] ignore) { var aProps = a.GetType().GetProperties(); var bProps = b.GetType().GetProperties(); int count = aProps.Count(); string aa, bb; for (int i = 0; i < count; i++) { aa = aProps[i].GetValue(a, null).ToStringNullSafe(); bb = bProps[i].GetValue(b, null).ToStringNullSafe(); if (aa != bb && ignore.Where(x => x == aProps[i].Name).Count() == 0) { return false; } } return true; } 

Para completar, quiero agregar una referencia a http://www.cyotek.com/blog/comparing-the-properties-of-two-objects-via-reflection Tiene una lógica más completa que la mayoría de las respuestas de otros en esta página.

Sin embargo, prefiero la biblioteca de Compare-Net-Objects https://github.com/GregFinzer/Compare-Net-Objects (referida por la respuesta de Liviu Trifoi )
La biblioteca tiene el paquete NuGet http://www.nuget.org/packages/CompareNETObjects y múltiples opciones para configurar.

Asegúrate de que los objetos no sean null .

Tener obj1 y obj2 :

 if(obj1 == null ) { return false; } return obj1.Equals( obj2 ); 

Esto funciona incluso si los objetos son diferentes. puedes personalizar los métodos en la clase de utilidades, tal vez quieras comparar propiedades privadas también …

 using System; using System.Collections.Generic; using System.Linq; using System.Text; class ObjectA { public string PropertyA { get; set; } public string PropertyB { get; set; } public string PropertyC { get; set; } public DateTime PropertyD { get; set; } public string FieldA; public DateTime FieldB; } class ObjectB { public string PropertyA { get; set; } public string PropertyB { get; set; } public string PropertyC { get; set; } public DateTime PropertyD { get; set; } public string FieldA; public DateTime FieldB; } class Program { static void Main(string[] args) { // create two objects with same properties ObjectA a = new ObjectA() { PropertyA = "test", PropertyB = "test2", PropertyC = "test3" }; ObjectB b = new ObjectB() { PropertyA = "test", PropertyB = "test2", PropertyC = "test3" }; // add fields to those objects a.FieldA = "hello"; b.FieldA = "Something differnt"; if (a.ComparePropertiesTo(b)) { Console.WriteLine("objects have the same properties"); } else { Console.WriteLine("objects have diferent properties!"); } if (a.CompareFieldsTo(b)) { Console.WriteLine("objects have the same Fields"); } else { Console.WriteLine("objects have diferent Fields!"); } Console.Read(); } } public static class Utilities { public static bool ComparePropertiesTo(this Object a, Object b) { System.Reflection.PropertyInfo[] properties = a.GetType().GetProperties(); // get all the properties of object a foreach (var property in properties) { var propertyName = property.Name; var aValue = a.GetType().GetProperty(propertyName).GetValue(a, null); object bValue; try // try to get the same property from object b. maybe that property does // not exist! { bValue = b.GetType().GetProperty(propertyName).GetValue(b, null); } catch { return false; } if (aValue == null && bValue == null) continue; if (aValue == null && bValue != null) return false; if (aValue != null && bValue == null) return false; // if properties do not match return false if (aValue.GetHashCode() != bValue.GetHashCode()) { return false; } } return true; } public static bool CompareFieldsTo(this Object a, Object b) { System.Reflection.FieldInfo[] fields = a.GetType().GetFields(); // get all the properties of object a foreach (var field in fields) { var fieldName = field.Name; var aValue = a.GetType().GetField(fieldName).GetValue(a); object bValue; try // try to get the same property from object b. maybe that property does // not exist! { bValue = b.GetType().GetField(fieldName).GetValue(b); } catch { return false; } if (aValue == null && bValue == null) continue; if (aValue == null && bValue != null) return false; if (aValue != null && bValue == null) return false; // if properties do not match return false if (aValue.GetHashCode() != bValue.GetHashCode()) { return false; } } return true; } } 

Actualice la respuesta anterior de Liviu: CompareObjects.DifferencesString ha quedado en desuso.

Esto funciona bien en una prueba unitaria:

 CompareLogic compareLogic = new CompareLogic(); ComparisonResult result = compareLogic.Compare(object1, object2); Assert.IsTrue(result.AreEqual); 

Este método obtendrá las properties de la clase y comparará los valores de cada property . Si alguno de los valores es diferente, return false , de lo contrario return true .

 public static bool Compare(T Object1, T object2) { //Get the type of the object Type type = typeof(T); //return false if any of the object is false if (Object1 == null || object2 == null) return false; //Loop through each properties inside class and get values for the property from both the objects and compare foreach (System.Reflection.PropertyInfo property in type.GetProperties()) { if (property.Name != "ExtensionData") { string Object1Value = string.Empty; string Object2Value = string.Empty; if (type.GetProperty(property.Name).GetValue(Object1, null) != null) Object1Value = type.GetProperty(property.Name).GetValue(Object1, null).ToString(); if (type.GetProperty(property.Name).GetValue(object2, null) != null) Object2Value = type.GetProperty(property.Name).GetValue(object2, null).ToString(); if (Object1Value.Trim() != Object2Value.Trim()) { return false; } } } return true; } 

Uso:

bool isEqual = Compare(Object1, Object2)

Para ampliar la respuesta de @nawfal, la uso para probar objetos de diferentes tipos en mis pruebas unitarias para comparar nombres de propiedades iguales. En mi caso, entidad de base de datos y DTO.

Usado así en mi prueba;

 Assert.IsTrue(resultDto.PublicInstancePropertiesEqual(expectedEntity)); public static bool PublicInstancePropertiesEqual(this T self, Z to, params string[] ignore) where T : class { if (self != null && to != null) { var type = typeof(T); var type2 = typeof(Z); var ignoreList = new List(ignore); var unequalProperties = from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance) where !ignoreList.Contains(pi.Name) let selfValue = type.GetProperty(pi.Name).GetValue(self, null) let toValue = type2.GetProperty(pi.Name).GetValue(to, null) where selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)) select selfValue; return !unequalProperties.Any(); } return self == null && to == null; } 

a veces no quiere comparar todas las propiedades públicas y desea comparar solo el subconjunto de ellas, por lo que en este caso puede mover la lógica para comparar la lista de propiedades deseada con la clase abstracta

 public abstract class ValueObject where T : ValueObject { protected abstract IEnumerable GetAttributesToIncludeInEqualityCheck(); public override bool Equals(object other) { return Equals(other as T); } public bool Equals(T other) { if (other == null) { return false; } return GetAttributesToIncludeInEqualityCheck() .SequenceEqual(other.GetAttributesToIncludeInEqualityCheck()); } public static bool operator ==(ValueObject left, ValueObject right) { return Equals(left, right); } public static bool operator !=(ValueObject left, ValueObject right) { return !(left == right); } public override int GetHashCode() { int hash = 17; foreach (var obj in this.GetAttributesToIncludeInEqualityCheck()) hash = hash * 31 + (obj == null ? 0 : obj.GetHashCode()); return hash; } } 

y usa esta clase abstracta más tarde para comparar los objetos

 public class Meters : ValueObject { ... protected decimal DistanceInMeters { get; private set; } ... protected override IEnumerable GetAttributesToIncludeInEqualityCheck() { return new List { DistanceInMeters }; } } 

mi solución está inspirada en la respuesta anterior de Aras Ain donde agregué un nivel de comparación de objetos y un objeto personalizado para los resultados de comparación. También me interesa obtener el nombre de la propiedad con el nombre del objeto:

  public static IEnumerable GetPublicSimplePropertiesChanged(this T previous, T proposedChange, string[] namesOfPropertiesToBeIgnored) where T : class { return GetPublicGenericPropertiesChanged(previous, proposedChange, namesOfPropertiesToBeIgnored, true, null, null); } public static IReadOnlyList GetPublicGenericPropertiesChanged(this T previous, T proposedChange, string[] namesOfPropertiesToBeIgnored) where T : class { return GetPublicGenericPropertiesChanged(previous, proposedChange, namesOfPropertiesToBeIgnored, false, null, null); } ///  /// Gets the names of the public properties which values differs between first and second objects. /// Considers 'simple' properties AND for complex properties without index, get the simple properties of the children objects. ///  ///  /// The previous object. /// The second object which should be the new one. /// The names of the properties to be ignored. /// if set to true consider simple types only. /// The parent type string. Meant only for recursive call with simpleTypeOnly set to true. /// when calling recursively, the current type of T must be clearly defined here, as T will be more generic (using base class). ///  /// the names of the properties ///  private static IReadOnlyList GetPublicGenericPropertiesChanged(this T previous, T proposedChange, string[] namesOfPropertiesToBeIgnored, bool simpleTypeOnly, string parentTypeString, Type secondType) where T : class { List propertiesChanged = new List(); if (previous != null && proposedChange != null) { var type = secondType == null ? typeof(T) : secondType; string typeStr = parentTypeString + type.Name + "."; var ignoreList = namesOfPropertiesToBeIgnored.CreateList(); IEnumerable> genericPropertiesChanged = from pi in type.GetProperties(BindingFlags.Public | BindingFlags.Instance) where !ignoreList.Contains(pi.Name) && pi.GetIndexParameters().Length == 0 && (!simpleTypeOnly || simpleTypeOnly && pi.PropertyType.IsSimpleType()) let firstValue = type.GetProperty(pi.Name).GetValue(previous, null) let secondValue = type.GetProperty(pi.Name).GetValue(proposedChange, null) where firstValue != secondValue && (firstValue == null || !firstValue.Equals(secondValue)) let subPropertiesChanged = simpleTypeOnly || pi.PropertyType.IsSimpleType() ? null : GetPublicGenericPropertiesChanged(firstValue, secondValue, namesOfPropertiesToBeIgnored, true, typeStr, pi.PropertyType) let objectPropertiesChanged = subPropertiesChanged != null && subPropertiesChanged.Count() > 0 ? subPropertiesChanged : (new ObjectPropertyChanged(proposedChange.ToString(), typeStr + pi.Name, firstValue.ToStringOrNull(), secondValue.ToStringOrNull())).CreateList() select objectPropertiesChanged; if (genericPropertiesChanged != null) { // get items from sub lists genericPropertiesChanged.ForEach(a => propertiesChanged.AddRange(a)); } } return propertiesChanged; } 

Usando la siguiente clase para almacenar resultados de comparación

 [System.Serializable] public class ObjectPropertyChanged { public ObjectPropertyChanged(string objectId, string propertyName, string previousValue, string changedValue) { ObjectId = objectId; PropertyName = propertyName; PreviousValue = previousValue; ProposedChangedValue = changedValue; } public string ObjectId { get; set; } public string PropertyName { get; set; } public string PreviousValue { get; set; } public string ProposedChangedValue { get; set; } } 

Y una prueba de unidad de muestra:

  [TestMethod()] public void GetPublicGenericPropertiesChangedTest1() { // Define objects to test Function func1 = new Function { Id = 1, Description = "func1" }; Function func2 = new Function { Id = 2, Description = "func2" }; FunctionAssignment funcAss1 = new FunctionAssignment { Function = func1, Level = 1 }; FunctionAssignment funcAss2 = new FunctionAssignment { Function = func2, Level = 2 }; // Main test: read properties changed var propertiesChanged = Utils.GetPublicGenericPropertiesChanged(funcAss1, funcAss2, null); Assert.IsNotNull(propertiesChanged); Assert.IsTrue(propertiesChanged.Count == 3); Assert.IsTrue(propertiesChanged[0].PropertyName == "FunctionAssignment.Function.Description"); Assert.IsTrue(propertiesChanged[1].PropertyName == "FunctionAssignment.Function.Id"); Assert.IsTrue(propertiesChanged[2].PropertyName == "FunctionAssignment.Level"); }