You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#:使用泛型与模板类型为任意类添加标识符属性

CRUD API中DTO重复问题的解决方案

问题背景

开发包含大量CRUD功能的API时,新增与编辑操作的DTO仅差一个标识记录的Id字段,随着代码库扩大,手动维护重复DTO的成本越来越高。尝试过继承基类但仍需额外声明类,希望通过类似JavaScript展开运算符的效果,用泛型给任意类添加Id属性且保持属性同层级(而非嵌套)。

新增地址DTO示例:

public class AddressDto
{
  public string AddressLine1 { get; set; }
  public string AddressLine2 { get; set; }
  public string City { get; set; }
  public string County { get; set; }
  public string Postcode { get; set; }
  //...
}

编辑地址DTO示例:

public class ExistingAddressDto
{
  public Guid Id { get; set; }
  public string AddressLine1 { get; set; }
  public string AddressLine2 { get; set; }
  public string City { get; set; }
  public string County { get; set; }
  public string Postcode { get; set; }
  //...
}

当前泛型尝试(不符合需求):

// 声明
public class Existing<T> where T : class
{
  public Guid Id { get; set; }
  public T Value { get; set; } // 不希望嵌套这个属性
}

// 使用
var existingAddress = new Existing<AddressDto>();

可行解决方案

1. T4模板自动生成DTO

用T4模板可以基于现有DTO自动生成带Id的版本,完全避免手动重复编写代码。

创建一个.tt文件,示例模板代码如下:

<#@ template language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Reflection" #>
<#@ output extension=".cs" #>

<#
// 扫描程序集中所有后缀为Dto且不是Existing开头的类
var dtoTypes = typeof(AddressDto).Assembly.GetTypes()
    .Where(t => t.Name.EndsWith("Dto") && !t.Name.StartsWith("Existing"));
foreach(var type in dtoTypes) {
#>
namespace YourNamespace // 替换为你的命名空间
{
    public class Existing<#= type.Name #>
    {
        public Guid Id { get; set; }
<#
    // 遍历原DTO的所有公共属性
    foreach(var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) {
#>
        public <#= prop.PropertyType.FullName #> <#= prop.Name #> { get; set; }
<#
    }
#>
    }
}
<#
    }
#>

添加该模板后,每次修改原DTO时,右键模板选择"运行自定义工具"即可自动更新带Id的DTO类,属性与原DTO完全同层级。

2. 自定义C#源生成器(C# 9+)

源生成器是编译时执行的工具,能实时生成代码,比T4更灵活,体验更好:

  • 创建一个源生成器类库项目,实现ISourceGenerator接口
  • 定义一个特性(如[GenerateExistingDto]),标记需要生成带Id版本的DTO
  • 在生成器逻辑中扫描标记特性的类,自动生成包含原属性+Id的ExistingXXXDto类

这种方式无需手动触发生成,编译时自动同步,适合大型项目使用。

3. 继承简化方案(折中)

如果不想引入代码生成工具,可以采用继承方式,仅需给编辑DTO添加Id属性,无需重复编写其他属性:

// 新增DTO(基类)
public class AddressDto
{
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
    public string City { get; set; }
    public string County { get; set; }
    public string Postcode { get; set; }
}

// 编辑DTO(仅声明继承和Id)
public class ExistingAddressDto : AddressDto
{
    public Guid Id { get; set; }
}

虽然仍需额外声明类,但代码量极少,维护成本低,适合小型项目或不想引入额外工具的场景。


为什么泛型嵌套不可行?

C#没有JavaScript的对象展开语法,泛型类无法将泛型参数T的属性直接提升到当前类层级,因此Existing<T>只能通过嵌套的Value属性访问原DTO内容,无法满足属性同层级的需求。

内容的提问来源于stack exchange,提问作者marcuthh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 18:54:52