【问题标题】:List of objects wont compare correctly when using "ObjectList.Contains(foo)"使用“Object List.Contains(foo)”时,对象列表无法正确比较
【发布时间】:2018-10-07 12:56:51
【问题描述】:

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 方法经过测试并且工作正常,返回书籍列表。 我是否需要某种额外的覆盖来获得

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

比较正确? 这是一个相当粗鲁的早期代码,请善待,仍有许多指标需要改进。

【问题讨论】:

  • 你应该重写Book类中的EqualsGetHashCode方法,这样Contains才能工作。

标签: c# list object compare contains


【解决方案1】:

有几种方法可以处理这个问题。 正如 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&lt;Book&gt;,其中包含NewBooks 中没有与OnlineUsersBooks 中的ISBN 匹配的所有书籍,而无需编写循环来获取它。
这种方法的另一个好处是它消除了对if(OblineUsersBooks.Count&gt;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;
    }

}

【讨论】:

  • 我刚刚偶然发现了“myList.Any(x => x.someProperty == someValue);”阅读本文前几秒钟的方法,但这是一个写得很好的答案!非常感谢!
  • 很高兴为您提供帮助 :-)
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    默认情况下,Collection.Contains 执行引用比较,因此只有当 ab 是同一个对象时,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 实现,但我已经展示了是一个好的开始。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-06-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多