C# 比较泛型方法中的列表/IEnumerable类型属性

C# 比较泛型方法中的列表/IEnumerable类型属性,c#,generics,reflection,C#,Generics,Reflection,我试图比较包含列表类型属性的对象。我能够比较简单的属性,但却被复杂的属性所困扰 foreach (PropertyInfo pi in properties) { object oldValue = pi.GetValue(oldObject), newValue = pi.GetValue(newObject); if (pi.PropertyType.IsGenericType && typeof(IEnumerable).IsAs

我试图比较包含
列表
类型属性的对象。我能够比较简单的属性,但却被复杂的属性所困扰

 foreach (PropertyInfo pi in properties)
     {
        object oldValue = pi.GetValue(oldObject), newValue = pi.GetValue(newObject);
        if (pi.PropertyType.IsGenericType && typeof(IEnumerable).IsAssignableFrom(pi.PropertyType))
        {
           Type type = oldValue.GetType().GetGenericArguments()[0];

           /* Need something like below commented line.*/
           // var added = newValue.Except(oldValue)
           // var removed = oldValue.Except(newValue);
        }}

在if块中,我需要在列表类型属性中找到添加和删除的对象。在对象中,我们有键属性属性来查找添加和删除的对象。

oldValue
newValue
转换为
IEnumerable
,然后根据需要比较它们:

if (IsGenericEnumerable(pi)) {
    IEnumerable<Object> newEnumerable = (IEnumerable<Object>) newValue;
    IEnumerable<Object> oldEnumerable = (IEnumerable<Object>) oldValue;

    // operate with newEnumerable and oldEnumerable as needed by the logic
    // ...
}
if(IsGenericEnumerable(pi)){
IEnumerable newEnumerable=(IEnumerable)newValue;
IEnumerable oldEnumerable=(IEnumerable)oldValue;
//根据逻辑需要使用newEnumerable和oldEnumerable进行操作
// ...
}

oldValue
newValue
转换为
IEnumerable
,然后根据需要进行比较:

if (IsGenericEnumerable(pi)) {
    IEnumerable<Object> newEnumerable = (IEnumerable<Object>) newValue;
    IEnumerable<Object> oldEnumerable = (IEnumerable<Object>) oldValue;

    // operate with newEnumerable and oldEnumerable as needed by the logic
    // ...
}
if(IsGenericEnumerable(pi)){
IEnumerable newEnumerable=(IEnumerable)newValue;
IEnumerable oldEnumerable=(IEnumerable)oldValue;
//根据逻辑需要使用newEnumerable和oldEnumerable进行操作
// ...
}

根据我对问题的理解,这里是完整的解决方案

以下是指定项的键属性的键属性:

[AttributeUsage(AttributeTargets.Property)]
public class KeyAttribute : Attribute
{
}
对于测试,假设我们有一个名为
SomeClass
的类,它包含一个
List
属性,还有一个名为
SomeItem
的项类,它包含一个键属性,通过比较忽略了其他属性:

public class SomeClass
{
    public List<SomeItem> Items { get; set; }
}

public class SomeItem
{
    [Key]
    public int TheKey { get; set; }
    public string SomeValue { get; set; }
}
public void CompareNewWithOld(object oldObject, object newObject, List<object> added, List<object> removed)
{
    var properties = typeof (SomeClass).GetProperties();

    foreach (PropertyInfo pi in properties)
    {
        object oldValue = pi.GetValue(oldObject), newValue = pi.GetValue(newObject);
        if (pi.PropertyType.IsGenericType && typeof(IEnumerable).IsAssignableFrom(pi.PropertyType))
        {
            var itemType = pi.PropertyType.GetGenericArguments()[0];
            var itemKeyProperty = itemType
                .GetProperties()
                .FirstOrDefault(ipi => ipi.GetCustomAttribute<KeyAttribute>() != null);

            if (itemKeyProperty == null)
            {
                continue; // no Key property -- cannot compare
            }

            var comparer = new ItemByKeyEqualityComparer(itemKeyProperty);

            HashSet<object> oldSet = new HashSet<object>(((IEnumerable)oldValue).Cast<object>(), comparer);
            HashSet<object> newSet = new HashSet<object>(((IEnumerable)newValue).Cast<object>(), comparer);

            HashSet<object> removedSet = new HashSet<object>(oldSet, comparer);
            removedSet.ExceptWith(newSet);

            HashSet<object> addedSet = new HashSet<object>(newSet, comparer);
            addedSet.ExceptWith(oldSet);

            added.AddRange(addedSet);
            removed.AddRange(removedSet);
        }
    }
}
公共类SomeClass
{
公共列表项{get;set;}
}
公共类项目
{
[关键]
公钥{get;set;}
公共字符串SomeValue{get;set;}
}
以下是执行比较的函数:

public class SomeClass
{
    public List<SomeItem> Items { get; set; }
}

public class SomeItem
{
    [Key]
    public int TheKey { get; set; }
    public string SomeValue { get; set; }
}
public void CompareNewWithOld(object oldObject, object newObject, List<object> added, List<object> removed)
{
    var properties = typeof (SomeClass).GetProperties();

    foreach (PropertyInfo pi in properties)
    {
        object oldValue = pi.GetValue(oldObject), newValue = pi.GetValue(newObject);
        if (pi.PropertyType.IsGenericType && typeof(IEnumerable).IsAssignableFrom(pi.PropertyType))
        {
            var itemType = pi.PropertyType.GetGenericArguments()[0];
            var itemKeyProperty = itemType
                .GetProperties()
                .FirstOrDefault(ipi => ipi.GetCustomAttribute<KeyAttribute>() != null);

            if (itemKeyProperty == null)
            {
                continue; // no Key property -- cannot compare
            }

            var comparer = new ItemByKeyEqualityComparer(itemKeyProperty);

            HashSet<object> oldSet = new HashSet<object>(((IEnumerable)oldValue).Cast<object>(), comparer);
            HashSet<object> newSet = new HashSet<object>(((IEnumerable)newValue).Cast<object>(), comparer);

            HashSet<object> removedSet = new HashSet<object>(oldSet, comparer);
            removedSet.ExceptWith(newSet);

            HashSet<object> addedSet = new HashSet<object>(newSet, comparer);
            addedSet.ExceptWith(oldSet);

            added.AddRange(addedSet);
            removed.AddRange(removedSet);
        }
    }
}
public void CompareNewWithOld(对象oldObject、对象newObject、添加列表、删除列表)
{
var properties=typeof(SomeClass).GetProperties();
foreach(PropertyInfo pi in properties)
{
对象oldValue=pi.GetValue(oldObject),newValue=pi.GetValue(newObject);
if(pi.PropertyType.IsGenericType&&typeof(IEnumerable).IsAssignableFrom(pi.PropertyType))
{
var itemType=pi.PropertyType.GetGenericArguments()[0];
var itemKeyProperty=itemType
.GetProperties()
.FirstOrDefault(ipi=>ipi.GetCustomAttribute()!=null);
如果(itemKeyProperty==null)
{
continue;//没有键属性--无法比较
}
var comparer=新的ItemByKeyEqualityComparer(itemKeyProperty);
HashSet oldSet=新HashSet(((IEnumerable)oldValue).Cast(),比较器);
HashSet newSet=newhashset(((IEnumerable)newValue).Cast(),comparer);
HashSet-removedSet=新HashSet(旧集,比较器);
removedSet.ExceptWith(新闻集);
HashSet addedSet=新的HashSet(newSet,comparer);
添加集(旧集除外);
add.AddRange(addedSet);
已删除。添加范围(removedSet);
}
}
}
为了方便地通过项对象的键属性与
HashSet
进行比较,我们还需要实现一个相等比较器类,如下所示:

public class ItemByKeyEqualityComparer : IEqualityComparer<object>
{
    private readonly PropertyInfo _keyProperty;
    public ItemByKeyEqualityComparer(PropertyInfo keyProperty)
    {
        _keyProperty = keyProperty;
    }
    public bool Equals(object x, object y)
    {
        var kx = _keyProperty.GetValue(x);
        var ky = _keyProperty.GetValue(y);
        if (kx == null)
        {
            return (ky == null);
        }
        return kx.Equals(ky);
    }
    public int GetHashCode(object obj)
    {
        var key = _keyProperty.GetValue(obj);
        return (key == null ? 0 : key.GetHashCode());
    }
}
公共类ItemByKeyEqualityComparer:IEqualityComparer
{
私有只读属性infou keyProperty;
public ItemByKeyEqualityComparer(PropertyInfo-keyProperty)
{
_keyProperty=keyProperty;
}
公共布尔等于(对象x、对象y)
{
var kx=_keyProperty.GetValue(x);
var ky=_keyProperty.GetValue(y);
如果(kx==null)
{
返回值(ky==null);
}
返回kx等于(ky);
}
public int GetHashCode(对象obj)
{
var key=_keyProperty.GetValue(obj);
返回(key==null?0:key.GetHashCode());
}
}
这是一个通过的测试:

[Test]
public void TestCompareNewWithOld()
{
    var oldObject = new SomeClass() {
        Items = new List<SomeItem>() {
            new SomeItem() { TheKey = 1, SomeValue = "A"},
            new SomeItem() { TheKey = 2, SomeValue = "B"},
            new SomeItem() { TheKey = 3, SomeValue = "C"},
            new SomeItem() { TheKey = 4, SomeValue = "D"},
        }
    };
    var newObject = new SomeClass() {
        Items = new List<SomeItem>() {
            new SomeItem() { TheKey = 3, SomeValue = "W"},
            new SomeItem() { TheKey = 4, SomeValue = "V"},
            new SomeItem() { TheKey = 5, SomeValue = "U"},
            new SomeItem() { TheKey = 6, SomeValue = "T"},
        }
    };

    var added = new List<object>();
    var removed = new List<object>();

    CompareNewWithOld(oldObject, newObject, added, removed);

    Assert.That(removed, Is.EquivalentTo(new[] {
        oldObject.Items[0],  //A
        oldObject.Items[1]   //B
    }));
    Assert.That(added, Is.EquivalentTo(new[] {
        newObject.Items[2],  //U
        newObject.Items[3]   //T
    }));
}
[测试]
public void TestCompareNewWithOld()
{
var oldObject=new SomeClass(){
Items=新列表(){
新建SomeItem(){TheKey=1,SomeValue=“A”},
新建SomeItem(){TheKey=2,SomeValue=“B”},
新建SomeItem(){TheKey=3,SomeValue=“C”},
新建SomeItem(){TheKey=4,SomeValue=“D”},
}
};
var newObject=newsomeclass(){
Items=新列表(){
新建SomeItem(){TheKey=3,SomeValue=“W”},
新建SomeItem(){TheKey=4,SomeValue=“V”},
新建SomeItem(){TheKey=5,SomeValue=“U”},
新建SomeItem(){TheKey=6,SomeValue=“T”},
}
};
var added=新列表();
var removed=新列表();
CompareNewWithOld(旧对象、新对象、添加、删除);
Assert.That(已删除)是.equaletto(新[]{
oldObject.Items[0],//A
oldObject.Items[1]//B
}));
Assert.That(已添加,即.equivalento(新[]{
newObject.Items[2],//U
newObject.Items[3]//T
}));
}

根据我对问题的理解,这里是完整的解决方案

以下是指定项的键属性的键属性:

[AttributeUsage(AttributeTargets.Property)]
public class KeyAttribute : Attribute
{
}
对于测试,假设我们有一个名为
SomeClass
的类,它包含一个
List
属性,还有一个名为
SomeItem
的项类,它包含一个键属性,通过比较忽略了其他属性:

public class SomeClass
{
    public List<SomeItem> Items { get; set; }
}

public class SomeItem
{
    [Key]
    public int TheKey { get; set; }
    public string SomeValue { get; set; }
}
public void CompareNewWithOld(object oldObject, object newObject, List<object> added, List<object> removed)
{
    var properties = typeof (SomeClass).GetProperties();

    foreach (PropertyInfo pi in properties)
    {
        object oldValue = pi.GetValue(oldObject), newValue = pi.GetValue(newObject);
        if (pi.PropertyType.IsGenericType && typeof(IEnumerable).IsAssignableFrom(pi.PropertyType))
        {
            var itemType = pi.PropertyType.GetGenericArguments()[0];
            var itemKeyProperty = itemType
                .GetProperties()
                .FirstOrDefault(ipi => ipi.GetCustomAttribute<KeyAttribute>() != null);

            if (itemKeyProperty == null)
            {
                continue; // no Key property -- cannot compare
            }

            var comparer = new ItemByKeyEqualityComparer(itemKeyProperty);

            HashSet<object> oldSet = new HashSet<object>(((IEnumerable)oldValue).Cast<object>(), comparer);
            HashSet<object> newSet = new HashSet<object>(((IEnumerable)newValue).Cast<object>(), comparer);

            HashSet<object> removedSet = new HashSet<object>(oldSet, comparer);
            removedSet.ExceptWith(newSet);

            HashSet<object> addedSet = new HashSet<object>(newSet, comparer);
            addedSet.ExceptWith(oldSet);

            added.AddRange(addedSet);
            removed.AddRange(removedSet);
        }
    }
}
公共类SomeClass
{
公共列表项{get;set;}
}
公共类项目
{
[关键]
公钥{get;set;}
公共字符串SomeValue{get;set;}
}
以下是执行比较的函数:

public class SomeClass
{
    public List<SomeItem> Items { get; set; }
}

public class SomeItem
{
    [Key]
    public int TheKey { get; set; }
    public string SomeValue { get; set; }
}
public void CompareNewWithOld(object oldObject, object newObject, List<object> added, List<object> removed)
{
    var properties = typeof (SomeClass).GetProperties();

    foreach (PropertyInfo pi in properties)
    {
        object oldValue = pi.GetValue(oldObject), newValue = pi.GetValue(newObject);
        if (pi.PropertyType.IsGenericType && typeof(IEnumerable).IsAssignableFrom(pi.PropertyType))
        {
            var itemType = pi.PropertyType.GetGenericArguments()[0];
            var itemKeyProperty = itemType
                .GetProperties()
                .FirstOrDefault(ipi => ipi.GetCustomAttribute<KeyAttribute>() != null);

            if (itemKeyProperty == null)
            {
                continue; // no Key property -- cannot compare
            }

            var comparer = new ItemByKeyEqualityComparer(itemKeyProperty);

            HashSet<object> oldSet = new HashSet<object>(((IEnumerable)oldValue).Cast<object>(), comparer);
            HashSet<object> newSet = new HashSet<object>(((IEnumerable)newValue).Cast<object>(), comparer);

            HashSet<object> removedSet = new HashSet<object>(oldSet, comparer);
            removedSet.ExceptWith(newSet);

            HashSet<object> addedSet = new HashSet<object>(newSet, comparer);
            addedSet.ExceptWith(oldSet);

            added.AddRange(addedSet);
            removed.AddRange(removedSet);
        }
    }
}
public void CompareNewWithOld(对象oldObject、对象newObject、添加列表、删除列表)
{
var properties=typeof(SomeClass).GetProperties();
foreach(PropertyInfo pi in properties)
{
对象oldValue=pi.GetValue(oldObject),newValue=pi.GetValue(newObject);
if(pi.PropertyType.IsGenericType&&typeo)