在 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; }
}
实现此目的的一种方法是以泛型方式定义 PersonA
到 DetailsA
之间的类型关系,并在 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();
我对 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; }
}
实现此目的的一种方法是以泛型方式定义 PersonA
到 DetailsA
之间的类型关系,并在 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();