【问题标题】:C# generic list of my class contains method not finding my instance我的类的 C# 通用列表包含找不到我的实例的方法
【发布时间】:2011-05-13 23:58:00
【问题描述】:

我们为我们的 CMS 设置了几个类,我正在尝试让平等工作,​​以便我可以检查通用列表是否包含项目。我们有一些继承层,我将在下面向您展示。在此之下,我将向您展示一些行为与我的预期相反的示例代码。如果您发现我做错了什么,请告诉我。我已经减少了下面的示例,只是为了向您展示相关的部分。我的实际课程要大得多,但我认为这是你需要看到的一切。

IBaseTemplate.cs

public interface IBaseTemplate {
  bool Equals(IBaseTemplate other);
  string GUID { get; }
}

BasePage.cs

public class BasePage : System.Web.UI.Page, IBaseTemplate, IEquatable<IBaseTemplate> {

  // code to define properties, including GUID

  // various constructors
  public BasePage(string GUID) { 
    this.GUID = GUID;
  }

  // interface methods
  public bool Equals(IBaseTemplate other) {
    return (this.GUID == other.GUID);
  }

}

LandingPage.cs

public class LandingPage : BasePage {
  // a bunch of extra properties and method specific to LandingPage
  // but NO definition for Equals since that's taken care of in BasePage

  public LandingPage(string GUID) : base(GUID) {}
}

SamplePage.aspx.cs

var p1 = new LandingPage("{3473AEF9-7382-43E2-B783-DB9B88B825C5}");
var p2 = new LandingPage("{3473AEF9-7382-43E2-B783-DB9B88B825C5}");
var p3 = new LandingPage("{3473AEF9-7382-43E2-B783-DB9B88B825C5}");
var p4 = new LandingPage("{3473AEF9-7382-43E2-B783-DB9B88B825C5}");

var coll = new List<LandingPage>();
coll.Add(p1);
coll.Add(p2);
coll.Add(p3);

p1.Equals(p4);     // True, as expected
coll.Contains(p4); // False, but I expect True here!

我希望coll.Contains(p4) 返回true,因为即使p1p4 是不同的实例,从BasePage 继承的Equals 方法会根据IBaseTemplate 的要求比较它们的GUID 属性。我错过了什么吗?

我查看了docs for List(T)'s Contains method,我正在实现IEquatable&lt;T&gt;.Equals,其中TIBaseTemplate

【问题讨论】:

  • 你应该可以在 Equals 方法中设置断点来查看值,你试过吗?

标签: c# equals contains generic-list iequatable


【解决方案1】:

您还需要覆盖Object.Equals(Object) - 请参阅this link

【讨论】:

  • 谢谢,你说得对。我在BasePage 中解决了它,实现了public override bool Equals(object obj)
  • 我还想指出,当您覆盖 Object.Equals 时,建议您也覆盖 GetHashCode()。根据文档,“只要没有对确定对象 Equals 方法返回值的对象状态进行修改,对象的 GetHashCode 方法就必须始终返回相同的哈希码。”请参阅msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx 了解更多信息。
  • 感谢反对派,我在输出中看到了该警告,并且也收到了!
【解决方案2】:

我敢打赌,在幕后,这行代码:

p1.Equals(p4)

 is actually calling upon the Equals method of `Object` rather than yours.

尝试让你的接口实现IEqualityComparer,并在你的接口定义中去掉显式的Equals

【讨论】:

  • "IEquatable 接口 ... 应该为可能存储在通用集合中的任何对象实现。" msdn.microsoft.com/en-us/library/ms131187.aspx - IEqualityComparer 用于将比较器放在您想要的任何东西上,而不仅仅是您要比较的类。不过,我认为您对 Object.Equals(Object) 的看法是正确的。
  • 它不会调用Object Equal 方法,因为它可以在类中找到它。
【解决方案3】:

如果您希望Contains 方法使用IEquatable.Equals 方法,您的BasePage 类型需要实现IEquatable&lt;BasePage&gt;,而不是IEquatable&lt;IBaseTemplate&gt;

This method determines equality by using the default equality comparer, as defined by the object's implementation of the IEquatable&lt;T&gt;.Equals method for T (the type of values in the list).

因为你的类没有实现IEquatable&lt;BasePage&gt;Contains 方法回退到使用虚拟的、非泛型的object.Equals 方法。在 BasePage 类中覆盖该方法应该可以解决问题

【讨论】:

    【解决方案4】:

    List.Contains(T item) 的调用使用默认相等比较器确定相等性,该比较器由对象对 T(列表中值的类型)的 IEquatable.Equals 方法的实现定义。

    因此,要在列表中定位的项目需要提供IEquatable 的实现,并且还需要重写Equals(Object obj) 方法以调用等价逻辑。

    举个例子,假设你有一个这样的界面:

    public interface IRole : INotifyPropertyChanged, INotifyPropertyChanging
    {
        #region ConcurrencyToken
        /// <summary>
        /// Gets the unique binary concurrency token for the security role.
        /// </summary>
        /// <value>
        /// A <see cref="IList{Byte}"/> collection that contains the unique binary concurrency token for the security role.
        /// </value>
        IList<byte> ConcurrencyToken
        {
            get;
        }
        #endregion
    
        #region Description
        /// <summary>
        /// Gets or sets the description of the security role.
        /// </summary>
        /// <value>
        /// The human readable description of the security role.
        /// </value>
        string Description
        {
            get;
            set;
        }
        #endregion
    
        #region Id
        /// <summary>
        /// Gets or sets the unique identifier for the security role.
        /// </summary>
        /// <value>
        /// A <see cref="Int32"/> value that represents the unique identifier for the security role.
        /// </value>
        int Id
        {
            get;
            set;
        }
        #endregion
    
        #region LastModifiedBy
        /// <summary>
        /// Gets or sets the name of the user or process that last modified the security role.
        /// </summary>
        /// <value>
        /// The name of the user or process that last modified the security role.
        /// </value>
        string LastModifiedBy
        {
            get;
            set;
        }
        #endregion
    
        #region LastModifiedOn
        /// <summary>
        /// Gets or sets the date and time at which the security role was last modified.
        /// </summary>
        /// <value>
        /// A <see cref="Nullable{DateTime}"/> that represents the date and time at which the security role was last modified.
        /// </value>
        DateTime? LastModifiedOn
        {
            get;
            set;
        }
        #endregion
    
        #region Name
        /// <summary>
        /// Gets or sets the name of the security role.
        /// </summary>
        /// <value>
        /// The human readable name of the security role.
        /// </value>
        string Name
        {
            get;
            set;
        }
        #endregion
    }
    

    这个接口的默认实现可能是这样的,注意IEquatable是如何实现的:

    public class Role : EntityBase, IRole, IEquatable<Role>
    {
        //=======================================================================================================
        //  Constructors
        //=======================================================================================================
        #region Role()
        /// <summary>
        /// Initializes a new instance of <see cref="Role"/> class.
        /// </summary>
        public Role()
        {
        }
        #endregion
    
        #region Role(IEnumerable<byte> concurrencyToken)
        /// <summary>
        /// Initializes a new instance of <see cref="Role"/> class 
        /// using the specified unique binary concurrency token.
        /// </summary>
        /// <param name="concurrencyToken">The unique binary concurrency token for the security role.</param>
        /// <exception cref="ArgumentNullException">The <paramref name="concurrencyToken"/> is a <see langword="null"/> reference (Nothing in Visual Basic).</exception>
        public Role(IEnumerable<byte> concurrencyToken) : base(concurrencyToken)
        {
    
        }
        #endregion
    
        //=======================================================================================================
        //  Public Methods
        //=======================================================================================================
        #region ToString()
        /// <summary>
        /// Returns a <see cref="String"/> that represents the current <see cref="Role"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="String"/> that represents the current <see cref="Role"/>.
        /// </returns>
        public override string ToString()
        {
            return this.Name;
        }
        #endregion
    
        //=======================================================================================================
        //  IEquatable<Role> Implementation
        //=======================================================================================================
        #region Equals(Role other)
        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns><see langword="true"/> if the current object is equal to the other parameter; otherwise, <see langword="false"/>.</returns>
        public bool Equals(Role other)
        {
            if (other == null)
            {
                return false;
            }
    
            if (!String.Equals(this.Description, other.Description, StringComparison.Ordinal))
            {
                return false;
            }
            else if (!Int32.Equals(this.Id, other.Id))
            {
                return false;
            }
            else if (!String.Equals(this.Name, other.Name, StringComparison.Ordinal))
            {
                return false;
            }
    
            return true;
        }
        #endregion
    
        #region Equals(object obj)
        /// <summary>
        /// Determines whether the specified <see cref="Object"/> is equal to the current <see cref="Object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="Object"/> to compare with the current <see cref="Object"/>.</param>
        /// <returns>
        /// <see langword="true"/> if the specified <see cref="Object"/> is equal to the current <see cref="Object"/>; otherwise, <see langword="false"/>.
        /// </returns>
        public override bool Equals(object obj)
        {
            return this.Equals(obj as Role);
        }
        #endregion
    
        #region GetHashCode()
        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>A 32-bit signed integer hash code.</returns>
        /// <a href="http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx"/>
        public override int GetHashCode()
        {
            int descriptionHashCode     = this.Description.GetHashCode();
            int idHashCode              = this.Id.GetHashCode();
            int nameHashCode            = this.Name.GetHashCode();
    
            /*
                * The 23 and 37 are arbitrary numbers which are co-prime.
                * 
                * The benefit of the below over the XOR (^) method is that if you have a type 
                * which has two values which are frequently the same, XORing those values 
                * will always give the same result (0) whereas the above will 
                * differentiate between them unless you're very unlucky.
            */
            int hashCode    = 23;
            hashCode        = hashCode * 37 + descriptionHashCode;
            hashCode        = hashCode * 37 + idHashCode;
            hashCode        = hashCode * 37 + nameHashCode;
    
            return hashCode;
        }
        #endregion
    
        //=======================================================================================================
        //  IRole Implementation
        //=======================================================================================================
        #region Description
        /// <summary>
        /// Gets or sets the description of this security role.
        /// </summary>
        /// <value>
        /// The human readable description of this security role. The default value is an <see cref="String.Empty"/> string.
        /// </value>
        [DataMember()]
        public string Description
        {
            get
            {
                return _roleDescription;
            }
    
            set
            {
                if (PropertyChangeNotifier.AreNotEqual(_roleDescription, value))
                {
                    using (new PropertyChangeNotifier(OnPropertyChanging, OnPropertyChanged))
                    {
                        _roleDescription = !String.IsNullOrEmpty(value) ? value : String.Empty;
                    }
                }
            }
        }
        private string _roleDescription = String.Empty;
        #endregion
    
        #region Id
        /// <summary>
        /// Gets or sets the unique identifier for this security role.
        /// </summary>
        /// <value>
        /// A <see cref="Int32"/> value that represents the unique identifier for this security role. 
        /// The default value is <i>zero</i>.
        /// </value>
        [DataMember()]
        public int Id
        {
            get
            {
                return _roleId;
            }
    
            set
            {
                if (PropertyChangeNotifier.AreNotEqual(_roleId, value))
                {
                    using (new PropertyChangeNotifier(OnPropertyChanging, OnPropertyChanged))
                    {
                        _roleId = value;
                    }
                }
            }
        }
        private int _roleId;
        #endregion
    
        #region Name
        /// <summary>
        /// Gets or sets the name of this security role.
        /// </summary>
        /// <value>
        /// The human readable name of this security role. The default value is an <see cref="String.Empty"/> string.
        /// </value>
        [DataMember()]
        public string Name
        {
            get
            {
                return _roleName;
            }
    
            set
            {
                if (PropertyChangeNotifier.AreNotEqual(_roleName, value))
                {
                    using (new PropertyChangeNotifier(OnPropertyChanging, OnPropertyChanged))
                    {
                        _roleName = !String.IsNullOrEmpty(value) ? value : String.Empty;
                    }
                }
            }
        }
        private string _roleName = String.Empty;
        #endregion
    }
    

    【讨论】:

      【解决方案5】:

      您需要将 p4 实例实际添加到集合中。

      coll.Add(p4);
      

      编辑

      为什么不只比较 GUID?

      coll.Add(p1.GUID);
      coll.Add(p2.GUID);
      coll.Add(p3.GUID);
      coll.Contains(p4.GUID);   //expect this to be true
      

      【讨论】:

      • 不,我正在尝试查看列表中的其他任何内容是否与p4GUID 具有相同的GUID
      • 因为我没有List&lt;string&gt; 我有List&lt;LandingPage&gt;。我已经做了大量工作来查询我们的 CMS 以获取 List&lt;LandingPage&gt;,因此没有理由将其重新转换为 List&lt;string&gt; 来进行比较。我需要使用LandingPage 的各种属性,所以我需要那个列表。
      猜你喜欢
      • 2020-05-30
      • 1970-01-01
      • 1970-01-01
      • 2021-10-02
      • 1970-01-01
      • 2019-04-21
      • 1970-01-01
      • 2019-08-24
      • 1970-01-01
      相关资源
      最近更新 更多