Список объектов не будет корректно сравниваться при использовании «ObjectList.Contains (foo)» - PullRequest
0 голосов
/ 26 апреля 2018

Книжный класс от HelperLibrary.Models.Book.cs

public class Book
{
    public string Title;
    public string Author;
    public string ISBN;

    public Book(string title, string author, string iSBN)
    {
        Title = title;
        Author = author;
        ISBN = iSBN;
    }

}

Вызов

private void SaveChanges_btn_Click(object sender, RoutedEventArgs e)
    {
        List<HelperLibrary.Models.Book> NewUsersBooks = new List<HelperLibrary.Models.Book>();

        foreach (var x in UserBooks_List.Items)
        {

            foreach(HelperLibrary.Models.Book y in App.GlobalBookList)
            {

                if (y.ISBN == x.ToString())
                {
                    NewUsersBooks.Add(y);
                }
            }

        }


        HelperLibrary.Helpers.SQLHelper.AddBookToUser(App.GlobalUserList[UserList_List.SelectedIndex], NewUsersBooks);


}

Вызов Sql из HelperLibrary.SqlHelper.cs

    public static void AddBookToUser(Models.User user, List<Models.Book> NewBooks)

    {
        List<Models.Book> OnlineUsersBooks = new List<Models.Book>();

        OnlineUsersBooks = GetUsersBooks(user);

        Debug.WriteLine("Online Count: " + OnlineUsersBooks.Count.ToString());

        if (OnlineUsersBooks.Count > 0)
        {


                foreach (Models.Book y in NewBooks)
                {

                    if (!(OnlineUsersBooks.Contains(y)))
                    {

                        using (SqlConnection connection = new SqlConnection(connectionString))
                        {
                            SqlCommand command = new SqlCommand("INSERT INTO Bookings VALUES (@UserId, @Title, @Author, @ISBN)", connection);
                            command.Parameters.AddWithValue("@UserId", user.GetUserID);
                            command.Parameters.AddWithValue("@Title", y.Title);
                            command.Parameters.AddWithValue("@Author", y.Author);
                            command.Parameters.AddWithValue("@ISBN", y.ISBN);

                            Debug.WriteLine(command.ToString());

                            command.Connection.Open();
                            command.ExecuteNonQuery();
                        }
                    }



            }

        }
        else
        {
            foreach (Models.Book y in NewBooks)
            {
                using (SqlConnection connection = new SqlConnection(connectionString))
                {
                    SqlCommand command = new SqlCommand("INSERT INTO Bookings VALUES (@UserId, @Title, @Author, @ISBN)", connection);
                    command.Parameters.AddWithValue("@UserId", user.GetUserID);
                    command.Parameters.AddWithValue("@Title", y.Title);
                    command.Parameters.AddWithValue("@Author", y.Author);
                    command.Parameters.AddWithValue("@ISBN", y.ISBN);

                    Debug.WriteLine(command.ToString());

                    command.Connection.Open();
                    command.ExecuteNonQuery();
                }
            }
        }

    }

Метод GetUserBooks протестирован и работает нормально, возвращая список книг. Нужно ли какое-то дополнительное переопределение, чтобы получить

    if (!(OnlineUsersBooks.Contains(y)))

тоже сравнивать правильно? Это довольно грубый код на ранней стадии, будьте добры, все же есть множество метрик для улучшения.

Ответы [ 3 ]

0 голосов
/ 26 апреля 2018

Чтобы заставить Contains возвращать что-то полезное, вам необходимо переопределить метод Equals в вашем классе. В противном случае сравнения выполняются с использованием сравнения ссылок (что означает, что оно вернет true, только если одна из книг в коллекции указывает на ту же область памяти, что и искомая книга).

Самый простой способ сделать это, вероятно, использовать ISBN, так как я считаю, что для книг это должен быть уникальный идентификатор. Но вы также можете использовать другие поля для сравнения, если хотите.

Обратите внимание, что при переопределении Equals вы также должны переопределить GetHashCode. Вот простой пример:

public class Book
{
    public string Title;
    public string Author;
    public string ISBN;

    public Book(string title, string author, string iSBN)
    {
        Title = title;
        Author = author;
        ISBN = iSBN;
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as Book);
    }

    protected bool Equals(Book other)
    {
        return string.Equals(ISBN, other?.ISBN);
    }

    public override int GetHashCode()
    {
        return ISBN?.GetHashCode() ?? 0;
    }
}
0 голосов
/ 26 апреля 2018

По умолчанию Collection.Contains выполняет сравнения ссылок, поэтому a == b имеет значение true, только если a и b являются одним и тем же объектом. Недостаточно иметь одинаковые значения полей.

Есть несколько разных способов решения этой проблемы, но если вы хотите, чтобы экземпляры сравнивались как равные, если значения их полей равны, вы хотите реализовать IEquatable:

public class Book : IEquatable<Book>
{
    public readonly string Title;
    public readonly string Author;
    public readonly string ISBN;

    public Book(string title, string author, string iSBN)
    {
        if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title));
        if (string.IsNullOrEmpty(author)) throw new ArgumentNullException(nameof(author));
        if (string.IsNullOrEmpty(iSBN)) throw new ArgumentNullException(nameof(iSBN));

        Title = title;
        Author = author;
        ISBN = iSBN;
    }

    public bool Equals(Book other)
    {
        return other != null
            && other.Title == Title && other.Author == Author && other.ISBN == ISBN;
    }

    protected override bool Equals(object other)
    {
        return Equals((Book)other);
    }

    public override int GetHashCode()
    {
        return Title.GetHashCode() ^ Author.GetHashCode() ^ ISBN.GetHashCode();
    }
}

В зависимости от того, как вы используете Book, вы можете захотеть превратить его поля в свойства, вы можете переопределить == и != и, возможно, захотите изменить реализацию GetHashCode, но то, что я показал хорошее начало.

0 голосов
/ 26 апреля 2018

Есть несколько способов справиться с этим. Один из способов, как написал Дэвид Грилач в своем ответе (теперь удален, но Руфус Л добавил еще один ответ, показывающий, как это сделать), - переопределить метод Equals - но я бы не рекомендовал этого, если вы действительно не знаете, что делаете , При переопределении метода Equals рекомендуется также переопределить метод GetHashCode - и это легко сделать неправильно.

Другой способ - изменить Contains на Find - , что, вероятно, будет самым простым способом сделать это:

if (OnlineUsersBooks.Find(b=> b.ISBN == y.ISBN)==null)

Использование метода Find позволяет использовать лямбда-выражение в качестве предиката, поэтому вам не нужно переопределять Equals или GetHashCode.

Еще один способ - использовать linq. Он очень мощный и не так сложен в освоении, и может помочь вам написать гораздо меньше кода, чем вы делаете сейчас.

Вот непроверенный пример того, как я мог бы получить книги, которые нужно вставить в базу данных с помощью linq:

var booksToAdd = NewBooks
    .Where(nb => !OnlineUsersBooks
        .Any(ob => ob.ISBN == nb.ISBN));

Он вернет IEnumerable<Book>, который содержит все книги в NewBooks, у которого нет соответствия ISBN в OnlineUsersBooks, без необходимости писать цикл для его получения.
Еще одним преимуществом этого подхода является то, что он устраняет необходимость в if(OblineUsersBooks.Count>0) - он будет работать так же с пустым списком.

Кроме того, как примечание, вы не должны использовать открытые поля. Вместо этого используйте общедоступные свойства ( Бонусное чтение: почему? ):

public class Book
{
    public string Title {get; set;} // Note the {get;set;} here.
    public string Author {get; set;}
    public string ISBN {get; set;}

    public Book(string title, string author, string iSBN)
    {
        Title = title;
        Author = author;
        ISBN = iSBN;
    }

}
...