2015-11-16 5 views
0

У меня есть метод, в котором я сравниваю свойства двух классов, которые используют один и тот же интерфейс. Существует несколько свойств, и вместо того, чтобы проверять их, я предпочел бы перебирать их, сохраняя повторяющееся кодирование. Я думал, что это возможно с отражением, но я не могу понять, как это сделать. Этот код не удался, поскольку я не понимал, что делаю.Сравнение свойств 2 объектов с одним и тем же интерфейсом

public void IsEqual(IPropertyComparer src, IPropertyComparer property) 
    { 
     PropertyInfo[] srcPI = src.GetType().GetPublicProperties(); 
     PropertyInfo[] propertyPI = property.GetType().GetPublicProperties(); 
     var numberOfProperties = srcPI.Count(); 
     for (var i = 0; i < numberOfProperties; i++) 
     { 
      var srcValue = srcPI[i].GetValue(src, null); 
      var propertyValue = propertyPI[i].GetValue(property, null); 
      var propertyName = srcPI[i].Name; 
      if(srcValue.Equals(propertyValue) == false) 
      { 
       this.Action = "ValidateExistingPropertyFailOn" + propertyName; 
       this.Data.Error = true; 
       this.Data.ErrorMessage = 
        string.Format(this.Template, this.UPRN, propertyName, srcValue, propertyValue); 
       return; 
      } 
     } 

В коде есть метод расширения. Метод расширения не работает, поскольку тип не распознается как интерфейс;

public static PropertyInfo[] GetPublicProperties(this Type type) 
{ 
    if (type.IsInterface) 
    { 
     var propertyInfos = new List<PropertyInfo>(); 

     var considered = new List<Type>(); 
     var queue = new Queue<Type>(); 
     considered.Add(type); 
     queue.Enqueue(type); 
     while (queue.Count > 0) 
     { 
      var subType = queue.Dequeue(); 
      foreach (var subInterface in subType.GetInterfaces()) 
      { 
       if (considered.Contains(subInterface)) 
       { 
        continue; 
       } 
       considered.Add(subInterface); 
       queue.Enqueue(subInterface); 
      } 

      var typeProperties = subType.GetProperties(
       BindingFlags.FlattenHierarchy 
       | BindingFlags.Public 
       | BindingFlags.Instance); 

      var newPropertyInfos = typeProperties 
       .Where(x => !propertyInfos.Contains(x)); 

      propertyInfos.InsertRange(0, newPropertyInfos); 
     } 

     return propertyInfos.ToArray(); 
    } 

    return type.GetProperties(BindingFlags.FlattenHierarchy 
     | BindingFlags.Public | BindingFlags.Instance); 
} 
+1

Как это «сбой»? Вы проследили его в отладчике, чтобы увидеть, где логика идет не так? У нас нет ваших ресурсов, чтобы иметь возможность воссоздать проблему. –

+1

Сторона примечания - есть несколько способов, чтобы это могло пойти не так. Что, если типы имеют намеренно названные свойства, не связанные с интерфейсом, которые означают разные вещи? Я бы просто создал сопоставитель, который является _специфичным_ в этом интерфейсе, проверяет каждое свойство и не беспокоиться о «повторяющемся» коде. Если у вас слишком много свойств, чтобы сделать это, то ваш интерфейс делает слишком много, –

+1

В дополнение к комментариям @ DStanley использование отражения (хотя оно работает, если выполнено правильно) значительно медленнее, чем прямое проверка свойств друг против друга. Делайте это только в коде, где вам не нужна производительность. – xxbbcc

ответ

1

Если я правильно понимаю, что вы пытаетесь сравнить два объекта, которые реализуют интерфейс в общем, и в этом сравнении только свойства, определенные в интерфейсе будет использовать для сравнения.

Если это так, то попробуйте следующее:

EDITED

Для предотвращения исключения нулевого отредактированные код:

bool IsEqual(IPropertyComparer o1, IPropertyComparer o2) 
{ 
    var props = typeof(IPropertyComparer).GetProperties(); 

    foreach(var prop in props) 
    { 
     var v1 = prop.GetValue(o1); 
     var v2 = prop.GetValue(o2); 

     if(v1 == null) 
     { 
      if(v2 != null) return false; 
     } 
     else 
     { 
      if(!v1.Equals(v2)) return false; 
     } 
    } 

    return true; 
} 
+0

Именно то, что я ищу. Благодарю. – arame3333

0

Я искал что-то вроде этого некоторые назад, и, наконец, удалось написать эту функцию. Он также должен работать для вашего интерфейса.

bool IsEqual(object obj) 
    { 
     var type = this.GetType(); 
     bool SameObj = true; 
     //for each public property from your class 
     type.GetProperties().ToList().ForEach(prop => 
     { 
      //dynamically checks there equals 
      if (!prop.GetValue(this, null).Equals(prop.GetValue(obj, null))) 
      { 
       SameObj = false; 
      } 
     }); 
     return SameObj; 
    } 
1

Если ваши потребности для сравнения двух экземпляров объектов становятся все более сложными, вы должны проверить этот пакет на NuGet:

https://www.nuget.org/packages/CompareNETObjects/

И Codeplex страницы:

http://comparenetobjects.codeplex.com/

Очень хорошо дает вам «разницу», которая может несколько раз быть тем, что вы действительно ищете ...

ОБНОВЛЕНИЕ - Общий, многоразовый способ проверки равенства, основанный на полях внутри объекта.

Я наткнулся на класс ValueObject<T>, написанный Джимми Богардом, который может заинтересовать некоторых людей. Его первоначальная цель хорошо подходит для DDD, где объекты с равными свойствами считаются равными, независимо от хэш-кода на основе ссылочного типа.

http://grabbagoft.blogspot.com/2007/06/generic-value-object-equality.html

Вот немного обновленная версия с помощью Linq для улучшения читабельности и лучшим способ обработки предусловий проверки - например x.ForNull (nameof (firstInCompare));

public abstract class ValueObject<T> : IEquatable<T> where T : ValueObject<T> 
{ 
    public override bool Equals(object obj) 
    { 
     if (obj == null) 
      return false; 

     var other = obj as T; 

     return Equals(other); 
    } 

    public override int GetHashCode() 
    { 
     var fields = GetFields(); 

     const int startValue = 17; 
     const int multiplier = 59; 

     return fields 
      .Select(field => field.GetValue(this)) 
      .Where(value => value != null) 
      .Aggregate(startValue, (current, value) => current * multiplier + value.GetHashCode()); 
    } 

    public virtual bool Equals(T other) 
    { 
     if (other == null) 
      return false; 

     var t = GetType(); 
     var otherType = other.GetType(); 

     if (t != otherType) 
      return false; 

     var fields = t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); 

     foreach (var field in fields) 
     { 
      var value1 = field.GetValue(other); 
      var value2 = field.GetValue(this); 

      if (value1 == null) 
      { 
       if (value2 != null) 
        return false; 
      } 
      else if (!value1.Equals(value2)) 
       return false; 
     } 

     return true; 
    } 

    private IEnumerable<FieldInfo> GetFields() 
    { 
     var t = GetType(); 

     t.ForNull("this"); 

     var fields = new List<FieldInfo>(); 

     while (t != typeof(object)) 
     { 
      if (t == null) continue; 
      fields.AddRange(t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)); 

      t = t.BaseType; 
     } 

     return fields; 
    } 

    public static bool operator ==(ValueObject<T> firstInCompare, ValueObject<T> secondInCompare) 
    { 
     firstInCompare.ForNull(nameof(firstInCompare)); 
     secondInCompare.ForNull(nameof(secondInCompare)); 

     return firstInCompare?.Equals(secondInCompare) ?? false; 
    } 

    public static bool operator !=(ValueObject<T> firstInCompare, ValueObject<T> secondInCompare) 
    { 
     return !(firstInCompare == secondInCompare); 
    } 
} 

Предпосылкой охранники могут быть найдены в статическом вспомогательном классе, как этот

public static class Guard 
{ 
    public static void ForLessEqualZero(this int value, string parameterName) 
    { 
     if (value <= 0) 
     { 
      throw new ArgumentOutOfRangeException(parameterName); 
     } 
    } 

    public static void ForPrecedesDate(this DateTime value, DateTime dateToPrecede, string parameterName) 
    { 
     if (value >= dateToPrecede) 
     { 
      throw new ArgumentOutOfRangeException(parameterName); 
     } 
    } 

    public static void ForNullOrEmpty(this string value, string parameterName) 
    { 
     if (string.IsNullOrEmpty(value)) 
     { 
      throw new ArgumentOutOfRangeException(parameterName); 
     } 
    } 

    public static void ForNull<T>(this T value, string parameterName) 
    { 
     ForValueType<T>(parameterName); 

     if (value == null) 
     { 
      throw new ArgumentNullException(parameterName); 
     } 
    } 

    private static void ForValueType<T>(string parameterName) 
    { 
     if (typeof(T).IsValueType) 
     { 
      throw new ArgumentException("parameter should be reference type, not value type", parameterName); 
     } 
    } 
} 

Для проверки равенства в общем, многоразовые, как вы могли бы обернуть свои объекты в ValueObject<T>.

Или вы можете украсть реализацию и сделать это более определенным образом.

Смежные вопросы