使用" ObjectList.Contains(foo)"时,对象列表无法正确比较

时间:2018-04-26 18:11:54

标签: c# list object compare contains

来自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);


}

来自HelperLibrary.SqlHelper.cs的SQL调用

    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方法测试并正常工作,返回一个Books列表。 我需要某种额外的覆盖才能获得

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

也正确比较? 这是一个相当粗糙的早期阶段代码,善良,仍然有很多指标需要改进。

3 个答案:

答案 0 :(得分:2)

有几种方法可以解决这个问题。 正如David Grilach在他的回答(现已删除,但Rufus L添加另一个答案显示如何做)中所写的一种方法是覆盖Equals方法 - 但我不建议除非你真的知道你是什么是做。当您覆盖Equals方法时,建议您也覆盖GetHashCode方法 - 这很容易做错。

另一种方法是将Contains更改为Find - ,这可能是您执行此操作的最简单方法:

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

使用Find方法可以将lambda表达式用作谓词,因此您根本不必覆盖EqualsGetHashCode

另一种方法是使用linq。它非常强大而且难以学习,它可以帮助您编写比现在更少的代码。

这是一个未经测试的例子,说明如何使用linq获取需要插入数据库的书籍:

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

它会返回一个IEnumerable<Book>,其中包含NewBooksOnlineUsersBooks内没有ISBN匹配的所有书籍,而您无需编写循环来获取它。<登记/> 这种方法的另一个好处是它消除了if(OblineUsersBooks.Count>0)的需要 - 它对空列表的作用相同。

另外,作为旁注,您不应使用公共字段。相反,请使用公共属性(Bonus reading: Why?):

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

}

答案 1 :(得分:0)

为了让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;
    }
}

答案 2 :(得分:0)

默认情况下,Collection.Contains执行引用比较,因此a == b仅在ab是同一对象时才为真。具有相同的字段值是不够的。

有几种不同的方法可以解决这个问题,但是如果你希望实例比较相等,如果它们的字段值相等,你想实现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实施,但我所展示的是一个良好的开端。