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