【问题标题】:How does List<T>.IndexOf() perform comparisons on custom objects?List<T>.IndexOf() 如何对自定义对象进行比较?
【发布时间】:2013-08-02 22:56:51
【问题描述】:

我编写了一个帐户对象类并持有这些帐户对象的静态List&lt;T&gt;。我的程序循环遍历列表中的每个帐户,对帐户执行一些工作,然后在到达列表末尾时在顶部重置。

我的问题是,在我的程序完成使用该帐户后,我需要能够将帐户重新插入到列表中,并添加了一些更新的信息。我可以按照下面的说明执行此操作,使用 IndexOf() 函数检查静态列表中的对象还是会因为我向其中添加数据而失败?我不明白它比较哪些字段以查看两个对象是否相同。

注意:列表中不允许重复,因此不存在更新错误项目的风险

public class Account
{
   public string name;
   public string password;
   public string newInfo;
}

public static class Resources
{
   private static List<Account> AccountList = new List<Account>();
   private static int currentAccountIndex = 0;

   public static Account GetNextAccount()
   {
      if (currentAccountIndex > AccountList.Count)
         currentAccountIndex = 0;
      return AccountList[currentAccountIndex++];
   }

   public static void UpdateAccount(Account account)
   {
      int index;
      if ((index = AccountList.IndexOf(account)) >= 0)
         AccountList[index] = account;
   }
}

public class Program
{
   public void PerformWork()
   {
      Account account = Resources.GetNextAccount();
      // Do some work
      account.newInfo = "foo";
      Resources.UpdateAccount(account);
   }
}

【问题讨论】:

  • 我已经编辑了你的标题。请参阅“Should questions include “tags” in their titles?”,其中的共识是“不,他们不应该”。
  • 你检查documentation了吗?它说“此方法使用默认相等比较器 EqualityComparer 确定相等。默认为 T,即列表中值的类型。”
  • 如果您在实施自定义IEqualityComparer 或拥有帐户支持IEquateable 后不关心订单,则最好使用HashSet 降神会,您没有任何重复.

标签: c# .net generics collections


【解决方案1】:

您可以为您的类使用自定义谓词,例如:

public class Account
{
  public string name;
  public string password;
  public string newInfo;

  public class IndexOfName
  {
    private string _match = "";

    public IndexOfName()
    {
    }

    public Predicate<Account> Match(string match)
    {
      this._match = match;
      return IsMatch;
    }

    private bool IsMatch(Account matchTo)
    {
      if (matchTo == null)
      {
        return false;
      }
      return matchTo.Equals(this._match);
    }
  }
}

那么你可以如下使用它:

Account.IndexOf indexOf = new Account.IndexOf();
int index;
if ((index = AccountList.FindIndex(indexOf.Match("john"))) > 0)
{
  // do something with John
}
if ((index = AccountList.FindIndex(indexOf.Match("jane"))) > 0)
{
  // do something with Jane
}

您甚至可以更改 IndeOfName 类以使用标志在您正在查找的信息类型之间切换。例如:名称或新信息。

【讨论】:

    【解决方案2】:

    如果您的类正确实现了IEquatable&lt;T&gt;,那么IndexOf() 将使用您的Equals() 方法来测试是否相等。

    否则,IndexOf() 将使用引用相等。

    【讨论】:

      【解决方案3】:

      另一个选择是使用List.FindIndex,并传递一个谓词。那就是:

      if ((index = AccountList.FindIndex(a => a.name == account.name)) >= 0)
          AccountList[index] = account;
      

      这样您就可以搜索任意字段或任意数量的字段。如果您无权访问 Account 的源代码以添加重载的 Equals 方法,这将特别有用。

      【讨论】:

        【解决方案4】:

        接受的答案没有涵盖的一件事是您应该覆盖 Equals(object)GetHashCode() 以使 IEquatable&lt;T&gt; 正常工作。这是完整的实现(基于keyboardP's answer

        public class Account : IEquatable<Account>
        {
            public string name;
            public string password;
            public string newInfo;
        
            private readonly StringComparer comparer = StringComparer.OrdinalIgnoreCase;
        
            public override bool Equals(object other)
            {
                //This casts the object to null if it is not a Account and calls the other Equals implementation.
                return this.Equals(other as Account);
            }
        
            public override int GetHashCode()
            {
                return comparer.GetHashCode(this.newInfo)
            }
        
            public bool Equals(Account other)
            {
               //Choose what you want to consider as "equal" between Account objects  
               //for example, assuming newInfo is what you want to consider a match
               //(regardless of case)
               if (other == null) 
                     return false;
        
               return comparer.Equals(this.newInfo, other.newInfo);
            }
        }
        

        【讨论】:

        • +1 - 这应该是公认的答案。 IndexOf 调用不使用它 AFAIK,这就是我的代码有效的原因,但对于一般用途,没有理由不这样做。
        • 谢谢我已经更新了接受的答案,以防其他人遇到它
        • 如上所示简单地覆盖GetHashCodeEquals 似乎就足够了,根本不需要实现IEquatable&lt;Account&gt;
        【解决方案5】:

        您的对象应实现IEquatable 接口并覆盖Equals 方法。

        public class Account : IEquatable<Account>
        {
            public string name;
            public string password;
            public string newInfo;
        
            public bool Equals(Account other)
            {
               //Choose what you want to consider as "equal" between Account objects  
               //for example, assuming newInfo is what you want to consider a match
               //(regardless of case)
               if (other == null) 
                     return false;
        
               return String.Equals(this.newInfo, other.newInfo, 
                                   StringComparison.OrdinalIgnoreCase);
            }
        }
        

        【讨论】:

        • 实际上,正确的接口是IEquatable&lt;T&gt;,因为它定义了Equals 方法,并由IndexOf 在泛型集合中使用。 IEqualityComparer&lt;T&gt; 由一个单独的对象使用,其唯一目的是比较两个 T 类型的对象。
        • 我非常感谢这个例子。我还是新手,把它作为一种爱好来学习,所以技术解释比代码更难可视化。
        • 您还需要覆盖GetHashCode(),请参阅IEquatable&lt;T&gt;页面的对实施者的说明部分:“如果您实施IEquatable&lt;T&gt;,您应该还要重写Object.Equals(Object)GetHashCode 的基类实现,使它们的行为与IEquatable&lt;T&gt;.Equals 方法的行为一致。"
        • @blizz - Scott 在他的回答中添加了额外的信息,这很重要(IndexOf 案例不需要,但无论如何都应该实施)。你可以取消我的答案并接受他的答案。
        • 您甚至不必实现IEquatable&lt;T&gt;。只需覆盖Equals(object)GetHashCode 即可。此外,在非密封类上实现IEquatable 也存在危险。见blog.mischel.com/2013/01/05/…
        猜你喜欢
        • 2015-10-23
        • 1970-01-01
        • 1970-01-01
        • 2018-09-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-01-12
        相关资源
        最近更新 更多