Отличный не работает с 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;
    }

}

Это основано на примере в "LINQ in Action". Листинг 4.16.

Это печатает Jon Skeet дважды. Зачем? Я даже попытался переопределить метод Equals в классе Author. Все еще Distinct, похоже, не работает. Что мне не хватает?

Изменить: Я добавил == и!= Перегрузку оператора. По-прежнему нет помощи.

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

Ответ 1

LINQ Distinct не настолько умный, когда речь идет о пользовательских объектах.

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

Один из способов - реализовать интерфейс IEquatable, как показано здесь.

Если вы измените свой авторский класс так, чтобы он работал.

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

Попробуйте это как DotNetFiddle

Ответ 2

Метод Distinct() проверяет ссылочное равенство для ссылочных типов. Это означает, что он ищет буквально тот же объект, который дублируется, а не разные объекты, которые содержат одинаковые значения.

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

Если вы хотите, чтобы Author обычно вел себя как обычный объект (т.е. только ссылочное равенство), но для целей отличительной проверки равенства по значениям имен используйте IEqualityComparer. Если вы всегда хотите сравнивать объекты Author, основанные на значениях имен, то переопределить GetHashCode и Equals или реализовать IEquatable.

Два члена в интерфейсе IEqualityComparer: Equals и GetHashCode. Ваша логика для определения того, являются ли два объекта Author равными, похоже, если строки First и Last name одинаковы.

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

Ответ 3

Другим решением без реализации IEquatable, Equals и GetHashCode является использование метода LINQs GroupBy и для выбора первого элемента из 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);
}

Ответ 4

Distinct() выполняет сравнение равенства по умолчанию для объектов в перечислимом. Если вы не переопределили Equals() и GetHashCode(), то он использует реализацию по умолчанию на object, которая сравнивает ссылки.

Простым решением является добавление правильной реализации Equals() и GetHashCode() для всех классов, которые участвуют в сопоставлении объекта (например, книги и автора).

Интерфейс IEqualityComparer - это удобство, позволяющее реализовать Equals() и GetHashCode() в отдельном классе, если у вас нет доступа к внутренним классам, которые вам нужны сравнить, или если вы используете другой метод сравнения.

Ответ 5

Вы превысили Equals(), но убедитесь, что вы также переопределите GetHashCode()

Ответ 6

Существует еще один способ получить различные значения из списка типов данных, определенных пользователем:

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

Конечно, он даст отличный набор данных

Ответ 7

Выше ответы неверны!!! Как указано в MSDN, возвращается стандартный экватор, который, как указано. Свойство Default проверяет, реализует ли тип T интерфейс System.IEquatable, и, если это так, возвращает EqualityComparer, который использует эту реализацию. В противном случае он возвращает EqualityComparer, который использует переопределения Object.Equals и Object.GetHashCode, предоставленные T

Это означает, что до тех пор, пока вы переопределяете Equals, вы в порядке.

Причина, по которой вы работаете, не работает, потому что вы проверяете firstname == lastname.

см. https://msdn.microsoft.com/library/bb348436(v=vs.100).aspx и https://msdn.microsoft.com/en-us/library/ms224763(v=vs.100).aspx