在c#中将类重构为多层泛型类

  • 本文关键字:泛型类 重构 c# generics
  • 更新时间 :
  • 英文 :


我有一个c#泛型的问题,我不确定最优雅的解决方案。我已经编程一段时间了,但对c#生态系统不熟悉,所以不知道搜索的常用术语。

我试图重构代码,以减少现有的复制-粘贴类的重复。用一级泛型很容易解决这个问题,但是用二级泛型我就解决不了。

下面是一个非常简单的例子。核心问题是BaseProfile不能使用与DetailsA或DetailsB相关的任何实现细节,因为它不知道类型。因此UpdateDetailsId()必须在2个派生类中复制,而不是让单个Profile类处理它。记住,这只是一个用来表达关系的小例子。真正的类有几十个字段,但我们在讨论的类中使用的是一个公共子集,所以即使DetailsA和DetailsB看起来相同,假设我们都需要。

public abstract class BaseProfile<TypeOfPerson>
{
public TypeOfPerson Person { get; set; }
}
public class Profile1 : BaseProfile<PersonA>
{
public void UpdateDetailsId(int id)
{
this.Person.Details.Id = id;
}
}
public class Profile2 : BaseProfile<PersonB>
{
public void UpdateDetailsId(int id)
{
this.Person.Details.Id = id;
}
}
public class PersonA
{
public DetailsA Details { get; set; }
}
public class PersonB
{
public DetailsB Details { get; set; }
}
public class DetailsA
{
public int Id { get; set; }
}
public class DetailsB
{
public int Id { get; set; }
}

我可以添加接口,因为它为每个类型引用所有相同的字段。然而,c#不允许一个接口包含另一个接口并在实现中自动解析它,因为成员必须完全匹配,也就是说,我认为我可以直接将IDetails Details添加到IPerson接口,但现在字段需要是IDetails类型,而不是实现IDetails的DetailsA。如果我这样做,那么我将失去编译器类型安全性,并且可能将错误的Details放在错误的Person上。

我已经成功地做了如下所示的公共/私有字段对,但这只在运行时将值转换为DetailsA时验证和抛出。我想要更安全的,但我不知道这是否是最好的选择。这个示例的目标是一个Profile类,处理多个Person类,每个类都有自己的Details类型,该类型有一个int Id字段。

public class PersonA : IPerson
{
public IDetails Details
{
get { return _details; }
set { _details = (DetailsA)value; }
}
private DetailsA _details { get; set; }
}

实现此目的的一种方法是以泛型方式定义PersonADetailsA之间的类型关系,并在BaseProfile上指定第二个泛型。

Profile1 : BaseProfile<PersonA, DetailsA>

考虑以下代码(注意,我使用的是Net6,所以我有所有这些可空引用类型操作符):

public abstract class BaseProfile<TPerson, TDetails>
where TDetails : IDetails, new()
where TPerson : PersonDetails<TDetails>, new()
{
public TPerson? Person { get; set; } = new TPerson();
public virtual void UpdateDetailsId(int id)
{
Person!.Details!.Id = id;
}
}
public class Profile1 : BaseProfile<PersonA, DetailsA>
{    
}
public class Profile2 : BaseProfile<PersonB, DetailsB>
{    
}
public abstract class PersonDetails<TDetails>
where TDetails : IDetails, new()
{
public virtual TDetails? Details { get; set; } = new TDetails();
}
public class PersonA : PersonDetails<DetailsA> 
{ 
}
public class PersonB : PersonDetails<DetailsB> 
{ 
}
public interface IDetails
{
int Id { get; set; }
}
public class DetailsA : IDetails
{
public int Id { get; set; }
public string? FirstName { get; set; }
}
public class DetailsB : IDetails
{
public int Id { get; set; }
public string? LastName { get; set; }
}

使用以下代码片段

进行测试
var profile1 = new Profile1();
var profile2 = new Profile2();
profile1.UpdateDetailsId(10);
profile2.UpdateDetailsId(12);
Console.WriteLine(profile1.Person!.Details!.Id);
Console.WriteLine(profile2.Person!.Details!.Id);
Console.WriteLine();

更新:

因为你包含了显式强制转换在Details属性getter和setter的代码片段中,我还想展示使用继承这些泛型类型的具体类型的模式——然后演示隐式/显式操作符用户定义的转换模式。

添加以下声明:


public abstract class BaseProfile<TPerson>
where TPerson : PersonDetails<GenericDetails>, new()
{
public TPerson? Person { get; set; } = new TPerson();
public virtual void UpdateDetailsId(int id)
{
Person!.Details!.Id = id;
}

public static explicit operator Profile1(BaseProfile<TPerson> details)
{
var profile = new Profile1();
profile.Person!.Details = (GenericDetails)details.Person!.Details!;
return profile;
}
public static explicit operator Profile2(BaseProfile<TPerson> details)
{
var profile = new Profile2();
profile.Person!.Details = (GenericDetails)details.Person!.Details!;
return profile;
}
}
public class GenericProfile : BaseProfile<GenericPerson>
{   
}
public abstract class GenericPersonDetails : PersonDetails<GenericDetails> 
{ 
}
public class GenericPerson : GenericPersonDetails 
{ 
}
public class GenericDetails : IDetails
{
public int Id { get; set; }
public static implicit operator DetailsA(GenericDetails details)
{
return new DetailsA() { Id = details.Id };
}
public static implicit operator DetailsB(GenericDetails details)
{
return new DetailsB() { Id = details.Id };
}
}

更新测试功能范围:


var profile1 = new Profile1();
var profile2 = new Profile2();
var genericProfile = new GenericProfile();
profile1.UpdateDetailsId(10);
profile2.UpdateDetailsId(12);
genericProfile.UpdateDetailsId(20);
Console.WriteLine(profile1.Person!.Details!.Id);
Console.WriteLine(profile1.Person!.Details!.FirstName ?? "No First Name");
Console.WriteLine(profile2.Person!.Details!.Id);
Console.WriteLine(profile2.Person!.Details!.LastName ?? "No Last Name");
Console.WriteLine(genericProfile.Person!.Details!.Id);
Console.WriteLine(((Profile1)genericProfile).Person!.Details!.FirstName ?? "No First Name");
Console.WriteLine(((Profile2)genericProfile).Person!.Details!.LastName ?? "No Last Name");

Console.WriteLine();

最新更新