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

Refactoring classes into multi-layered generic classes in C#

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

我正在尝试重构代码以减少 classes 的现有复制粘贴重复。用一级泛型很容易解决,但我不能用两个泛型。

下面是一个非常简单的例子。核心问题是 BaseProfile 无法使用与 DetailsA 或 DetailsB 相关的任何实现细节,因为它不知道类型。因此 UpdateDetailsId() 必须在 2 个派生的 class 中复制,而不是让一个配置文件 class 处理它。请记住,这只是一个用来表达关系的玩具示例。真正的 classes 有几十个字段,但是我们在有问题的 class 中使用的一个公共子集,所以即使 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。如果我这样做,那么我将失去编译器类型安全性,并且可以将错误的详细信息放在错误的人身上。

我已经成功地完成了如下所示的 public/private 字段对,但这仅在将值转换为 DetailsA 时在运行时进行验证和抛出。我更喜欢更安全的东西,但我不知道这是否是最佳选择。这个例子的目标是一个个人资料 class,处理多个人 classes,每个人都有自己的 Details 类型,有一个 int Id 字段。

public class PersonA : IPerson
{

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

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

Profile1 : BaseProfile<PersonA, DetailsA>

考虑以下代码(请注意,我使用的是 Net6,因此我拥有所有这些可为 null 的引用类型运算符):

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();

更新:

因为您在详细信息 属性 getter 和 setter 的片段中包含 显式转换 ,我还想展示一个使用具体类型继承的模式在这些通用类型上——然后演示 implicit/explicit operator user-defined 转换模式。

添加以下声明:


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();