9

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

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??
}

最后,我想要一个Difference像这样的 s 列表:

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

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

4

5 回答 5

6

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

代码:

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类中表示这些差异。

于 2013-06-23T01:24:02.157 回答
3

我们从 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();
于 2013-06-21T03:38:10.607 回答
2

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

为什么不老派简单。

有一个 GetDifferences 成员函数 Person。

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

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

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

于 2013-06-24T15:31:51.950 回答
0

我正在使用这个来做到这一点:

    //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;
    }
于 2013-06-17T21:11:33.463 回答
0

在这里,您有一个代码可以满足您的需求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;
    }
于 2013-06-22T17:48:08.890 回答