web-dev-qa-db-fra.com

Distinct ne fonctionne pas avec LINQ to Objects

class Program
{
    static void Main(string[] args)
    {
        List<Book> books = new List<Book> 
        {
            new Book
            {
                Name="C# in Depth",
                Authors = new List<Author>
                {
                    new Author 
                    {
                        FirstName = "Jon", LastName="Skeet"
                    },
                     new Author 
                    {
                        FirstName = "Jon", LastName="Skeet"
                    },                       
                }
            },
            new Book
            {
                Name="LINQ in Action",
                Authors = new List<Author>
                {
                    new Author 
                    {
                        FirstName = "Fabrice", LastName="Marguerie"
                    },
                     new Author 
                    {
                        FirstName = "Steve", LastName="Eichert"
                    },
                     new Author 
                    {
                        FirstName = "Jim", LastName="Wooley"
                    },
                }
            },
        };


        var temp = books.SelectMany(book => book.Authors).Distinct();
        foreach (var author in temp)
        {
            Console.WriteLine(author.FirstName + " " + author.LastName);
        }

        Console.Read();
    }

}
public class Book
{
    public string Name { get; set; }
    public List<Author> Authors { get; set; }
}
public class Author
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public override bool Equals(object obj)
    {
        return true;
        //if (obj.GetType() != typeof(Author)) return false;
        //else return ((Author)obj).FirstName == this.FirstName && ((Author)obj).FirstName == this.LastName;
    }

}

Ceci est basé sur un exemple dans "LINQ in Action". Listing 4.16.

Cela imprime deux fois Jon Skeet. Pourquoi? J'ai même essayé de surcharger la méthode Equals dans la classe Author. Still Distinct ne semble pas fonctionner. Qu'est-ce que je rate?

Edit: J'ai aussi ajouté == et! = Surcharge d'opérateur. Toujours pas d'aide.

 public static bool operator ==(Author a, Author b)
    {
        return true;
    }
    public static bool operator !=(Author a, Author b)
    {
        return false;
    }
108
Tanmoy

LINQ Distinct n’est pas aussi intelligent en ce qui concerne les objets personnalisés.

Tout ce que cela fait est de regarder votre liste et de voir qu'elle a deux objets différents (peu importe qu'ils aient les mêmes valeurs pour les champs membres).

Une solution consiste à implémenter l'interface IEquatable comme indiqué ici .

Si vous modifiez votre classe Author, cela devrait fonctionner.

public class Author : IEquatable<Author>
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public bool Equals(Author other)
    {
        if (FirstName == other.FirstName && LastName == other.LastName)
            return true;

        return false;
    }

    public override int GetHashCode()
    {
        int hashFirstName = FirstName == null ? 0 : FirstName.GetHashCode();
        int hashLastName = LastName == null ? 0 : LastName.GetHashCode();

        return hashFirstName ^ hashLastName;
    }
}

Essayez comme DotNetFiddle

143
skalb

La méthode Distinct() vérifie l'égalité de référence pour les types de référence. Cela signifie qu'il recherche littéralement le même objet dupliqué, et non des objets différents contenant les mêmes valeurs.

Il existe un surcharge qui prend un IEqualityComparer , vous pouvez donc spécifier une logique différente pour déterminer si un objet donné est égal à un autre.

Si vous voulez que Author se comporte normalement comme un objet normal (c’est-à-dire une seule égalité de référence), mais aux fins de vérification distincte des valeurs d’égalité par nom, utilisez un IEqualityComparer Si vous souhaitez toujours que les objets Auteur soient comparés en fonction des valeurs de nom, alors substituez GetHashCode et Equals , ou IEquatable .

Les deux membres de l'interface IEqualityComparer sont Equals et GetHashCode. Votre logique pour déterminer si deux objets Author sont égaux semble l'être si les chaînes Prénom et Nom sont identiques.

public class AuthorEquals : IEqualityComparer<Author>
{
    public bool Equals(Author left, Author right)
    {
        if((object)left == null && (object)right == null)
        {
            return true;
        }
        if((object)left == null || (object)right == null)
        {
            return false;
        }
        return left.FirstName == right.FirstName && left.LastName == right.LastName;
    }

    public int GetHashCode(Author author)
    {
        return (author.FirstName + author.LastName).GetHashCode();
    }
}
63
Rex M

Une autre solution sans implémenter IEquatable, Equals et GetHashCode consiste à utiliser la méthode LINQs GroupBy et à sélectionner le premier élément de l'IGrouping.

var temp = books.SelectMany(book => book.Authors)
                .GroupBy (y => y.FirstName + y.LastName )
                .Select (y => y.First ());

foreach (var author in temp){
  Console.WriteLine(author.FirstName + " " + author.LastName);
}
42
Jehof

Il existe un autre moyen d'obtenir des valeurs distinctes à partir d'une liste de types de données définis par l'utilisateur:

YourList.GroupBy(i => i.Id).Select(i => i.FirstOrDefault()).ToList();

Assurément, cela donnera un ensemble distinct de données

24
Ashu_90

Distinct() effectue la comparaison d'égalité par défaut sur les objets de l'énumérable. Si vous n'avez pas remplacé Equals() et GetHashCode() , il utilise alors l'implémentation par défaut de object, qui compare les références.

La solution simple consiste à ajouter une implémentation correcte de Equals() et GetHashCode() à toutes les classes qui participent au graphe d'objets que vous comparez (c.-à-d. Book et Author).

L’interface IEqualityComparer est une commodité qui vous permet de mettre en œuvre Equals() et GetHashCode() dans une classe séparée lorsque vous n'avez pas accès aux éléments internes des classes à comparer, ou si vous utilisez une méthode de comparaison différente.

20
AndyM

Vous avez substitué Equals (), mais assurez-vous également de remplacer GetHashCode ()

10
Eric King

Les réponses ci-dessus sont fausses !!! Distinct comme indiqué sur MSDN renvoie l'équateur par défaut qui, comme indiqué La propriété Default vérifie si le type T implémente l'interface System.IEquatable et, le cas échéant, renvoie un EqualityComparer qui utilise cette implémentation. Sinon, il retourne un EqualityComparer qui utilise les substitutions de Object.Equals et Object.GetHashCode fournies par T

Ce qui veut dire que tant que vous êtes sur égaux, tout va bien.

Votre code ne fonctionne pas parce que vous cochez prénom == nom.

voir https://msdn.Microsoft.com/library/bb348436 (v = vs.100) .aspx et https://msdn.Microsoft.com/en-us/library /ms224763(v=vs.100).aspx

7
Alex