C# 如何优雅地检查类层次结构中的相等性,这些类有一个包含主键的公共基类?

C# 如何优雅地检查类层次结构中的相等性,这些类有一个包含主键的公共基类?,c#,equality,C#,Equality,背景 我有一个基类,它保存用于ORM(Microsoft实体框架)的整数ID。由此派生的类大约有25个,继承层次结构最多有4个类 要求 我需要能够测试这个层次结构中的一个对象是否等于另一个对象。要使ID相等,ID必须相同,但这还不够。例如,如果两个Person对象具有不同的ID,则它们不相等,但如果它们具有相同的ID,则它们可能相等,也可能不相等 算法 为了实现C#Equals方法,您必须检查: 提供的对象不为null 它必须与此对象的类型相同 IDs必须匹配 除此之外,必须比较所有其他属性

背景

我有一个基类,它保存用于ORM(Microsoft实体框架)的整数ID。由此派生的类大约有25个,继承层次结构最多有4个类

要求

我需要能够测试这个层次结构中的一个对象是否等于另一个对象。要使ID相等,ID必须相同,但这还不够。例如,如果两个
Person
对象具有不同的ID,则它们不相等,但如果它们具有相同的ID,则它们可能相等,也可能不相等

算法

为了实现C#
Equals
方法,您必须检查:

  • 提供的对象不为null
  • 它必须与
    对象的类型相同
  • ID
    s必须匹配
除此之外,必须比较所有其他属性,除非在两个对象相同的特殊情况下

实施

    /// <summary>
    /// An object which is stored in the database
    /// </summary>
    public abstract class DatabaseEntity
    {
        /// <summary>
        /// The unique identifier; if zero (0) then the ID is not assigned
        /// </summary>
        public int ID { get; set; }

        public override bool Equals(object obj)
        {
            if (obj == null)
            {
                return false;
            }

            if (ReferenceEquals(obj, this))
            {
                return true;
            }

            if (obj.GetType() != GetType())
            {
                return false;
            }

            DatabaseEntity databaseEntity = (DatabaseEntity)obj;
            if (ID != databaseEntity.ID)
            {
                return false;
            }

            return EqualsIgnoringID(databaseEntity);
        }

        public override int GetHashCode()
        {
            return ID;
        }

        /// <summary>
        /// Check if this object is equal to the supplied one, disregarding the IDs
        /// </summary>
        /// <param name="databaseEntity">another object, which should be of the same type as this one</param>
        /// <returns>true if they are equal (disregarding the ID)</returns>
        protected abstract bool EqualsIgnoringID(DatabaseEntity databaseEntity);
    }

    public class Person : DatabaseEntity
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public override bool EqualsIgnoringID(DatabaseEntity databaseEntity)
        {
            Person person = (Person)databaseEntity;
            return person.FirstName == FirstName && person.LastName == LastName;
        }
    }

    public class User: Person
    {
        public string Password { get; set; }

        public override bool EqualsIgnoringID(DatabaseEntity databaseEntity)
        {
            User user = (User)databaseEntity;
            return user.Password == Password;
        }
    }
//
///存储在数据库中的对象
/// 
公共抽象类数据库实体
{
/// 
///唯一标识符;如果为零(0),则不分配ID
/// 
公共int ID{get;set;}
公共覆盖布尔等于(对象对象对象)
{
if(obj==null)
{
返回false;
}
if(ReferenceEquals(obj,this))
{
返回true;
}
if(obj.GetType()!=GetType())
{
返回false;
}
DatabaseEntity DatabaseEntity=(DatabaseEntity)obj;
if(ID!=databaseEntity.ID)
{
返回false;
}
返回EqualSigningId(数据库实体);
}
公共覆盖int GetHashCode()
{
返回ID;
}
/// 
///检查此对象是否等于提供的对象,忽略ID
/// 
///另一个对象,其类型应与此对象相同
///如果它们相等,则为true(忽略ID)
受保护的抽象布尔EqualSigningId(DatabaseEntity DatabaseEntity);
}
公共类人员:DatabaseEntity
{
公共字符串名{get;set;}
公共字符串LastName{get;set;}
public override bool equalSigningId(数据库实体数据库实体)
{
个人=(个人)数据库实体;
返回person.FirstName==FirstName&&person.LastName==LastName;
}
}
公共类用户:Person
{
公共字符串密码{get;set;}
public override bool equalSigningId(数据库实体数据库实体)
{
用户用户=(用户)数据库实体;
返回user.Password==密码;
}
}
评论


我最不喜欢这个解决方案的特点是显式转换。是否有一种替代方案可以避免在每个类中重复所有公共逻辑(检查null、type等)

使用泛型非常简单:

public abstract class Entity<T>
{
  protected abstract bool IsEqual(T other);
}

public class Person : Entity<Person>
{
  protected override bool IsEqual(Person other) { ... }
}
公共抽象类实体
{
受保护的抽象布尔等值(T其他);
}
公共类人员:实体
{
受保护的覆盖布尔IsEqual(其他人){…}
}
这适用于一个继承级别,或者除最后一个级别外,所有级别都是
abstract

如果这对你来说还不够好,你必须做出决定:

  • 如果它不是那么常见,那么可以将少数异常保留为手动强制转换
  • 如果这很普遍,你就不走运了。使
    Person
    成为泛型是可行的,但它有点违背了目的-它要求您在需要使用
    Person
    时指定具体的
    Person
    派生类型。这可以通过使用非通用的接口
    IPerson
    来处理。当然,实际上,这仍然意味着
    Person
    是抽象的-您无法构建
    Person
    的非具体版本。事实上,为什么它不是抽象的呢?您是否可以拥有一个不属于
    派生类型之一的
    ?这听起来是个坏主意

这很容易使用泛型:

public abstract class Entity<T>
{
  protected abstract bool IsEqual(T other);
}

public class Person : Entity<Person>
{
  protected override bool IsEqual(Person other) { ... }
}
公共抽象类实体
{
受保护的抽象布尔等值(T其他);
}
公共类人员:实体
{
受保护的覆盖布尔IsEqual(其他人){…}
}
这适用于一个继承级别,或者除最后一个级别外,所有级别都是
abstract

如果这对你来说还不够好,你必须做出决定:

  • 如果它不是那么常见,那么可以将少数异常保留为手动强制转换
  • 如果这很普遍,你就不走运了。使
    Person
    成为泛型是可行的,但它有点违背了目的-它要求您在需要使用
    Person
    时指定具体的
    Person
    派生类型。这可以通过使用非通用的接口
    IPerson
    来处理。当然,实际上,这仍然意味着
    Person
    是抽象的-您无法构建
    Person
    的非具体版本。事实上,为什么它不是抽象的呢?您是否可以拥有一个不属于
    派生类型之一的
    ?这听起来是个坏主意

如果不使用
abstract
,而是继续覆盖子类的
Equals
方法,则看起来更简单。然后您可以这样扩展:

public class Person : DatabaseEntity
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public override bool Equals(object other)
    {
        if (!base.Equals(other))
            return false;
        Person person = (Person)other;
        return person.FirstName == FirstName && person.LastName == LastName;
    }
}
您必须强制转换到
Person
,但这适用于相对较少的代码行和没有任何worri的长层次结构