【问题标题】:Compare Properties automatically自动比较属性
【发布时间】:2013-06-17 20:06:34
【问题描述】:

我想获取为匹配对象而更改的所有属性的名称。我有这些(简化的)类:

public enum PersonType { Student, Professor, Employee }

class Person {
    public string Name { get; set; }
    public PersonType Type { get; set; }
}

class Student : Person {
     public string MatriculationNumber { get; set; }
}

class Subject {
     public string Name { get; set; }
     public int WeeklyHours { get; set; }
}

class Professor : Person {
    public List<Subject> Subjects { get; set; }
}

现在我想获取属性值不同的对象:

List<Person> oldPersonList = ...
List<Person> newPersonList = ...
List<Difference> = GetDifferences(oldPersonList, newPersonList);

public List<Difference> GetDifferences(List<Person> oldP, List<Person> newP) {
     //how to check the properties without casting and checking 
     //for each type and individual property??
     //can this be done with Reflection even in Lists??
}

最后我想要一个Differences 的列表,如下所示:

class Difference {
    public List<string> ChangedProperties { get; set; }
    public Person NewPerson { get; set; }
    public Person OldPerson { get; set; }
}

ChangedProperties 应包含已更改属性的名称。

【问题讨论】:

  • 对列表执行此操作是一个真正的痛苦(假设您需要处理添加/删除/重新排序/等);但是,在每个对象的基础上,请参阅:stackoverflow.com/questions/3060382/… - 正是这样做的
  • @MarcGravell:我试过了,它返回作为增量列表的属性。还是谢谢。
  • 你关心不在两个对象中的属性,即将人与学生进行比较时,应将 matriculationNumber 视为变化吗?
  • 我将这些人与Name 属性匹配,然后在找到匹配项时进行比较。所以只能比较相同类型的对象。
  • 由于 Subjects 是一个列表,如果 1 位教授在列表中有 2 个主题元素,而其他教授只有 1 或 3 个,那么额外或更少的主题元素会算作差异吗?

标签: c# properties comparison


【解决方案1】:

我花了很长时间尝试使用类型化委托编写一个更快的基于反射的解决方案。但最终我放弃了,转而使用Marc Gravell'sFast-Member library,以实现比普通反射更高的性能。

代码:

internal class PropertyComparer
{    
    public static IEnumerable<Difference<T>> GetDifferences<T>(PropertyComparer pc,
                                                               IEnumerable<T> oldPersons,
                                                               IEnumerable<T> newPersons)
        where T : Person
    {
        Dictionary<string, T> newPersonMap = newPersons.ToDictionary(p => p.Name, p => p);
        foreach (T op in oldPersons)
        {
            // match items from the two lists by the 'Name' property
            if (newPersonMap.ContainsKey(op.Name))
            {
                T np = newPersonMap[op.Name];
                Difference<T> diff = pc.SearchDifferences(op, np);
                if (diff != null)
                {
                    yield return diff;
                }
            }
        }
    }

    private Difference<T> SearchDifferences<T>(T obj1, T obj2)
    {
        CacheObject(obj1);
        CacheObject(obj2);
        return SimpleSearch(obj1, obj2);
    }

    private Difference<T> SimpleSearch<T>(T obj1, T obj2)
    {
        Difference<T> diff = new Difference<T>
                                {
                                    ChangedProperties = new List<string>(),
                                    OldPerson = obj1,
                                    NewPerson = obj2
                                };
        ObjectAccessor obj1Getter = ObjectAccessor.Create(obj1);
        ObjectAccessor obj2Getter = ObjectAccessor.Create(obj2);
        var propertyList = _propertyCache[obj1.GetType()];
        // find the common properties if types differ
        if (obj1.GetType() != obj2.GetType())
        {
            propertyList = propertyList.Intersect(_propertyCache[obj2.GetType()]).ToList();
        }
        foreach (string propName in propertyList)
        {
            // fetch the property value via the ObjectAccessor
            if (!obj1Getter[propName].Equals(obj2Getter[propName]))
            {
                diff.ChangedProperties.Add(propName);
            }
        }
        return diff.ChangedProperties.Count > 0 ? diff : null;
    }

    // cache for the expensive reflections calls
    private Dictionary<Type, List<string>> _propertyCache = new Dictionary<Type, List<string>>();
    private void CacheObject<T>(T obj)
    {
        if (!_propertyCache.ContainsKey(obj.GetType()))
        {
            _propertyCache[obj.GetType()] = new List<string>();
            _propertyCache[obj.GetType()].AddRange(obj.GetType().GetProperties().Select(pi => pi.Name));
        }
    }
}

用法:

PropertyComparer pc = new PropertyComparer();
var diffs = PropertyComparer.GetDifferences(pc, oldPersonList, newPersonList).ToList();

性能:

我非常有偏见的测量表明,这种方法比 Json-Conversion 快大约 4-6 倍,比普通反射快大约 9 倍。但公平地说,您可能会加快其他解决方案的速度。

限制:

目前我的解决方案不会递归嵌套列表,例如它不会比较单个 Subject 项目 - 它只检测主题列表不同,但不检测什么或在哪里。但是,在需要时添加此功能应该不会太难。最困难的部分可能是决定如何在 Difference 类中表示这些差异。

【讨论】:

    【解决方案2】:

    我们从两个简单的方法开始:

    public bool AreEqual(object leftValue, object rightValue)
    {
        var left = JsonConvert.SerializeObject(leftValue);
        var right = JsonConvert.SerializeObject(rightValue);
    
        return left == right;
    }
    
    public Difference<T> GetDifference<T>(T newItem, T oldItem)
    {
        var properties = typeof(T).GetProperties();
    
        var propertyValues = properties
            .Select(p => new { 
                p.Name, 
                LeftValue = p.GetValue(newItem), 
                RightValue = p.GetValue(oldItem) 
            });
    
        var differences = propertyValues
            .Where(p => !AreEqual(p.LeftValue, p.RightValue))
            .Select(p => p.Name)
            .ToList();
    
        return new Difference<T>
        {
            ChangedProperties = differences,
            NewItem = newItem,
            OldItem = oldItem
        };
    }
    

    AreEqual 只是使用 Json.Net 比较两个对象的序列化版本,这使它不会区别对待引用类型和值类型。

    GetDifference 检查传入对象的属性并单独比较它们。

    要获取差异列表:

    var oldPersonList = new List<Person> { 
        new Person { Name = "Bill" }, 
        new Person { Name = "Bob" }
    };
    
    var newPersonList = new List<Person> {
        new Person { Name = "Bill" },
        new Person { Name = "Bobby" }
    };
    
    var diffList = oldPersonList.Zip(newPersonList, GetDifference)
        .Where(d => d.ChangedProperties.Any())
        .ToList();
    

    【讨论】:

      【解决方案3】:

      每个人都总是想花哨地编写这些提取数据的过于通用的方法。这是有代价的。

      为什么不老派简单。

      有一个 GetDifferences 成员函数 Person。

       virtual List<String> GetDifferences(Person otherPerson){
         var diffs = new List<string>();
         if(this.X != otherPerson.X) diffs.add("X");
         ....
       }
      

      在继承的类中。覆盖并添加它们的特定属性。 AddRange 基函数。

      KISS - 保持简单。编写它需要你 10 分钟的猴子工作,而且你知道它会高效且有效。

      【讨论】:

      • 这行得通。但这不会需要10分钟。我的课程比我给出的例子复杂得多。至少需要一天的时间,其中包含数百行代码以及该代码中可能存在的错误。
      • 然后确保称量性能。如果比较次数少,则反射路径很好。如果你运行这个超过数十万,那么我会更加小心。
      【解决方案4】:

      我是用这个来做的:

          //This structure represents the comparison of one member of an object to the corresponding member of another object.
          public struct MemberComparison
          {
              public static PropertyInfo NullProperty = null; //used for ROOT properties - i dont know their name only that they are changed
      
              public readonly MemberInfo Member; //Which member this Comparison compares
              public readonly object Value1, Value2;//The values of each object's respective member
              public MemberComparison(PropertyInfo member, object value1, object value2)
              {
                  Member = member;
                  Value1 = value1;
                  Value2 = value2;
              }
      
              public override string ToString()
              { 
                  return Member.name+ ": " + Value1.ToString() + (Value1.Equals(Value2) ? " == " : " != ") + Value2.ToString();
              }
          }
      
          //This method can be used to get a list of MemberComparison values that represent the fields and/or properties that differ between the two objects.
          public static List<MemberComparison> ReflectiveCompare<T>(T x, T y)
          {
              List<MemberComparison> list = new List<MemberComparison>();//The list to be returned
      
              if (x.GetType().IsArray)
              {
                  Array xArray = x as Array;
                  Array yArray = y as Array;
                  if (xArray.Length != yArray.Length)
                      list.Add(new MemberComparison(MemberComparison.NullProperty, "array", "array"));
                  else
                  {
                      for (int i = 0; i < xArray.Length; i++)
                      {
                          var compare = ReflectiveCompare(xArray.GetValue(i), yArray.GetValue(i));
                          if (compare.Count > 0)
                              list.AddRange(compare);
                      }
                  }
              }
              else
              {
                  foreach (PropertyInfo m in x.GetType().GetProperties())
                      //Only look at fields and properties.
                      //This could be changed to include methods, but you'd have to get values to pass to the methods you want to compare
                      if (!m.PropertyType.IsArray && (m.PropertyType == typeof(String) || m.PropertyType == typeof(double) || m.PropertyType == typeof(int) || m.PropertyType == typeof(uint) || m.PropertyType == typeof(float)))
                      {
                          var xValue = m.GetValue(x, null);
                          var yValue = m.GetValue(y, null);
                          if (!object.Equals(yValue, xValue))//Add a new comparison to the list if the value of the member defined on 'x' isn't equal to the value of the member defined on 'y'.
                              list.Add(new MemberComparison(m, yValue, xValue));
                      }
                      else if (m.PropertyType.IsArray)
                      {
                          Array xArray = m.GetValue(x, null) as Array;
                          Array yArray = m.GetValue(y, null) as Array;
                          if (xArray.Length != yArray.Length)
                              list.Add(new MemberComparison(m, "array", "array"));
                          else
                          {
                              for (int i = 0; i < xArray.Length; i++)
                              {
                                  var compare = ReflectiveCompare(xArray.GetValue(i), yArray.GetValue(i));
                                  if (compare.Count > 0)
                                      list.AddRange(compare);
                              }
                          }
                      }
                      else if (m.PropertyType.IsClass)
                      {
                          var xValue = m.GetValue(x, null);
                          var yValue = m.GetValue(y, null);
                          if ((xValue == null || yValue == null) && !(yValue == null && xValue == null))
                              list.Add(new MemberComparison(m, xValue, yValue));
                          else if (!(xValue == null || yValue == null))
                          {
                              var compare = ReflectiveCompare(m.GetValue(x, null), m.GetValue(y, null));
                              if (compare.Count > 0)
                                  list.AddRange(compare);
                          }
      
      
                      }
              }
              return list;
          }
      

      【讨论】:

      • 当我使用这个时,我得到一个 Parameter count mismatch 异常。
      【解决方案5】:

      这里有一个代码,可以用Reflection 做你想做的事情。

          public List<Difference> GetDifferences(List<Person> oldP, List<Person> newP)
          {
              List<Difference> allDiffs = new List<Difference>();
              foreach (Person oldPerson in oldP)
              {
                  foreach (Person newPerson in newP)
                  {
                      Difference curDiff = GetDifferencesTwoPersons(oldPerson, newPerson);
                      allDiffs.Add(curDiff);
                  }
              }
      
              return allDiffs;
          }
      
          private Difference GetDifferencesTwoPersons(Person OldPerson, Person NewPerson)
          {
              MemberInfo[] members = typeof(Person).GetMembers();
      
              Difference returnDiff = new Difference();
              returnDiff.NewPerson = NewPerson;
              returnDiff.OldPerson = OldPerson;
              returnDiff.ChangedProperties = new List<string>();
              foreach (MemberInfo member in members)
              {
                  if (member.MemberType == MemberTypes.Property)
                  {
                      if (typeof(Person).GetProperty(member.Name).GetValue(NewPerson, null).ToString() != typeof(Person).GetProperty(member.Name).GetValue(OldPerson, null).ToString())
                      {
                          returnDiff.ChangedProperties.Add(member.Name);
                      }
                  }
              }
      
              return returnDiff;
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多