使用 "ObjectList.Contains(foo)" 时对象列表无法正确比较
List of objects wont compare correctly when using "ObjectList.Contains(foo)"
从 HelperLibrary.Models.Book.cs
预订 Class
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)))
比较正确?
这是一个相当粗鲁的早期代码,客气一点,还有很多指标需要改进。
有几种方法可以处理这个问题。
一种方法,正如 David Grilach 在他的回答中所写(现已删除,但 Rufus L 添加了另一个显示如何做的答案),是覆盖 Equals
方法 - 但我不建议这样做,除非你真的知道你是什么是做。当您覆盖 Equals
方法时,建议同时覆盖 GetHashCode
方法 - 这样做很容易出错。
另一种方法是将 Contains
更改为 Find
- 这可能是最简单的方法:
if (OnlineUsersBooks.Find(b=> b.ISBN == y.ISBN)==null)
使用 Find
方法允许您使用 lambda 表达式作为谓词,因此您根本不必覆盖 Equals
或 GetHashCode
。
还有一种方法是使用 linq。它非常强大而且不难学习,而且它可以帮助您编写比现在少得多的代码。
这是一个未经测试的示例,说明我如何使用 linq 获取您需要插入数据库的书籍:
var booksToAdd = NewBooks
.Where(nb => !OnlineUsersBooks
.Any(ob => ob.ISBN == nb.ISBN));
它会 return 一个包含 NewBooks
中所有在 OnlineUsersBooks
中没有 ISBN 匹配的书的 IEnumerable<Book>
,而您不必编写循环获取它。
这种方法的另一个好处是它消除了对 if(OblineUsersBooks.Count>0)
的需要——它对空列表也同样有效。
此外,作为旁注,您不应使用 public 字段。相反,使用 public 属性 (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;
}
}
为了使 Contains
return 有用,您需要覆盖 class 上的 Equals
方法。否则,比较是使用参考比较完成的(这意味着如果集合中的一本书指向与您要查找的书相同的内存位置,它只会 return 为真)。
执行此操作的最简单方法可能是使用 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;
}
}
默认情况下,Collection.Contains
执行引用比较,因此只有当 a
和 b
是同一对象时,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
实施,但我所展示的是一个良好的开端。
从 HelperLibrary.Models.Book.cs
预订 Classpublic 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)))
比较正确? 这是一个相当粗鲁的早期代码,客气一点,还有很多指标需要改进。
有几种方法可以处理这个问题。
一种方法,正如 David Grilach 在他的回答中所写(现已删除,但 Rufus L 添加了另一个显示如何做的答案),是覆盖 Equals
方法 - 但我不建议这样做,除非你真的知道你是什么是做。当您覆盖 Equals
方法时,建议同时覆盖 GetHashCode
方法 - 这样做很容易出错。
另一种方法是将 Contains
更改为 Find
- 这可能是最简单的方法:
if (OnlineUsersBooks.Find(b=> b.ISBN == y.ISBN)==null)
使用 Find
方法允许您使用 lambda 表达式作为谓词,因此您根本不必覆盖 Equals
或 GetHashCode
。
还有一种方法是使用 linq。它非常强大而且不难学习,而且它可以帮助您编写比现在少得多的代码。
这是一个未经测试的示例,说明我如何使用 linq 获取您需要插入数据库的书籍:
var booksToAdd = NewBooks
.Where(nb => !OnlineUsersBooks
.Any(ob => ob.ISBN == nb.ISBN));
它会 return 一个包含 NewBooks
中所有在 OnlineUsersBooks
中没有 ISBN 匹配的书的 IEnumerable<Book>
,而您不必编写循环获取它。
这种方法的另一个好处是它消除了对 if(OblineUsersBooks.Count>0)
的需要——它对空列表也同样有效。
此外,作为旁注,您不应使用 public 字段。相反,使用 public 属性 (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;
}
}
为了使 Contains
return 有用,您需要覆盖 class 上的 Equals
方法。否则,比较是使用参考比较完成的(这意味着如果集合中的一本书指向与您要查找的书相同的内存位置,它只会 return 为真)。
执行此操作的最简单方法可能是使用 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;
}
}
默认情况下,Collection.Contains
执行引用比较,因此只有当 a
和 b
是同一对象时,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
实施,但我所展示的是一个良好的开端。