Linq За исключением пользовательских IEqualityComparer - PullRequest
27 голосов
/ 12 августа 2011

Я пытаюсь найти разницу между двумя общими списками, как в примере ниже.Хотя t1 и t2 содержат одинаковые свойства, они не являются одним и тем же объектом, поэтому мне нужно реализовать IEqualityComparer.

Это похоже на работу с этим примером, но реальный класс имеет несколько других свойств иМне также нужно сделать то же самое с несколькими другими классами.

Так что мне было интересно, если я заново изобрету колесо?

Есть ли более простой способ сравнения всех свойств двухобъекты?На данный момент мне действительно нужно справиться только с классом, содержащим простые типы, но было бы неплохо, если бы у меня был компаратор, который работал с классами, содержащими экземпляры других классов.

void Main()
{
    var t1 = new Sizes { Name = "Test" , Size = 1} ;
    var t2 = new Sizes { Name = "Test" , Size = 1} ;

    var list1 = new List<Sizes>();
    var list2 = new List<Sizes>();
    list1.Add(t1);
    list2.Add(t2);

    var differences = list2.Except(list1 , new SizesComparer());    
    // differences should be empty.
}


public class Sizes  
{
    public string Name { get;  set; }
    public int    Size { get;  set; }
}

public class SizesComparer : IEqualityComparer<Sizes>   
{
    bool IEqualityComparer<Sizes>.Equals(Sizes x, Sizes y)
    {            
        return (x.Name.Equals(y.Name) && x.Size.Equals(y.Size));        
    }

    int IEqualityComparer<Sizes>.GetHashCode(Sizes obj)
    {
        if (Object.ReferenceEquals(obj, null))
            return 0;               

        return obj.Name.GetHashCode() + obj.Size;       
    }
}

Ответы [ 3 ]

52 голосов
/ 12 августа 2011

Вы можете попробовать что-то вроде:

var differences = list2.Where(l2 => 
    !list1.Any(l1 => l1.Name == l2.Name && l1.Size == l2.Size));

Или, если хотите,

var differences = list2.Where(l2 => 
    list1.All(l1 => l1.Name != l2.Name || l1.Size != l2.Size));
4 голосов
/ 17 августа 2011

Решение, которое я в итоге использовал, не может быть описано как быстрое, но это не мое дело, и оно делает то, что я хочу, потому что оно может быть использовано повторно и не ограничено каким-либо конкретным классом.

Он использует библиотеку Newtonsoft.Json для сериализации объекта в строку, а затем сравнивает результат. Это также имеет преимущество работы с анонимными классами и вложенными классами.

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

public class JSonEqualityComparer<T> : IEqualityComparer<T>
{   
    public bool Equals(T x, T y)
    {           
        return String.Equals
        ( 
            Newtonsoft.Json.JsonConvert.SerializeObject(x), 
            Newtonsoft.Json.JsonConvert.SerializeObject(y)
        );                  
    }

    public int GetHashCode(T obj)
    {                           
        return Newtonsoft.Json.JsonConvert.SerializeObject(obj).GetHashCode();          
    }               
}       


public static partial class LinqExtensions
{
    public static IEnumerable<T> ExceptUsingJSonCompare<T>
        (this IEnumerable<T> first, IEnumerable<T> second)
    {   
        return first.Except(second, new JSonEqualityComparer<T>());
    }
}

Чтобы использовать его, вы меняете Except на ExceptUsingJSonCompare, например:

var differences = list2.ExceptUsingJSonCompare(list1); 
0 голосов
/ 12 августа 2011

Лучший способ, которым я обычно пользуюсь, это попытаться использовать уникальный / составной ключ в каждом объекте, и таким образом вы сравниваете только часть ключа и не должны сравнивать другие биты.

Вы можете использовать Reflection, который будет проходить через все открытые свойства класса (которые будут очень общими) и сравнивать их, но это будет медленнее.

...