web-dev-qa-db-fra.com

Linq Except avec IEqualityComparer personnalisé

J'essaie de trouver la différence entre deux listes génériques, comme dans l'exemple ci-dessous. Même si t1 et t2 contiennent les mêmes propriétés, ce ne sont pas le même objet, j'ai donc besoin d'implémenter un IEqualityComparer.

Cela semble fonctionner avec cet exemple, mais la vraie classe a plusieurs autres propriétés et j'ai également besoin de faire la même chose avec quelques autres classes.

Je me demandais donc si je réinventais la roue?

Existe-t-il une méthode plus simple pour comparer toutes les propriétés de deux objets? Pour le moment, je n'ai vraiment besoin que de faire face à une classe contenant des types simples, mais ce serait bien d'avoir un comparateur qui fonctionne avec des classes contenant des instances d'autres classes.

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;       
    }
}
28
sgmoore

La solution que j'ai fini par utiliser ne pouvait pas être décrite comme rapide, mais ce n'est pas un problème pour moi et elle fait ce que je veux en ce sens qu'elle peut être réutilisée et n'est pas limitée à une classe particulière.

Il utilise la bibliothèque Newtonsoft.Json pour sérialiser l'objet en une chaîne, puis compare le résultat. Cela présente également l'avantage de travailler avec des classes anonymes et des classes imbriquées.

Je suppose que la façon dont la comparaison fonctionne est qu'elle appelle d'abord GetHashCode sur les deux objets et s'ils correspondent, elle appelle ensuite Equals, ce qui signifie que dans cette routine, les objets correspondants seront sérialisés deux fois.

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>());
    }
}

Pour l'utiliser, vous échangez Except avec ExceptUsingJSonCompare, par exemple:

var differences = list2.ExceptUsingJSonCompare(list1); 
4
sgmoore

Vous pouvez essayer quelque chose comme:

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

Ou si vous préférez:

var differences = list2.Where(l2 => 
    list1.All(l1 => l1.Name != l2.Name || l1.Size != l2.Size));
52
Chris Snowden

La meilleure façon de faire est d'essayer d'avoir une clé unique/composite dans chaque objet et de cette façon, vous ne comparez que la partie clé et n'avez pas à comparer d'autres bits.

Vous pouvez utiliser Reflection qui passera par chaque propriété publique de la classe (qui sera très générique) et les comparera, mais ce sera plus lent.

0
Bek Raupov