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

C#中如何基于Trainee record生成Manager record对象?

问题描述

现有C# record定义如下:

public abstract record Employee
{
    public int EmployeeId { get; init; }
}

public record Manager : Employee
{
    public string AdminCode { get; init; }
}

public record Trainee : Employee
{
    public int ManagerId { get; init; }
}

需要基于已有的Trainee对象创建Manager对象,无需手动复制基类Employee中的EmployeeId等字段。尝试过with表达式结合类型转换的方式无效(二次转换返回null)。

实际场景是用带init属性的不可变record作为JSON契约DTO,大量DTO共享基类字段,希望复用Trainee的初始化逻辑生成Manager,避免重复代码。


解决方案

方法1:封装基类字段映射逻辑

利用基类统一处理共享属性的复制,派生类只需补充自身独有的字段:

public abstract record Employee
{
    public int EmployeeId { get; init; }

    // 封装基类属性的复制逻辑,供派生类调用
    protected Employee CopyBaseProperties(Employee target)
    {
        return target with { EmployeeId = this.EmployeeId };
    }
}

public record Trainee : Employee
{
    public int ManagerId { get; init; }

    // 直接复用基类逻辑生成Manager
    public Manager ToManager(string adminCode)
    {
        var manager = new Manager { AdminCode = adminCode };
        return (Manager)CopyBaseProperties(manager);
    }
}

调用示例:

var trainee = new Trainee { EmployeeId = 123, ManagerId = 456 };
var manager = trainee.ToManager("ADMIN001");

这种方式类型安全,适合基类字段不多的场景,完全避免重复写属性复制代码。

方法2:反射批量复制属性

如果基类/派生类字段较多,手动映射太繁琐,可以用反射批量匹配同名同类型的可初始化属性:

public static class RecordConverter
{
    public static TTarget Convert<TSource, TTarget>(TSource source)
        where TSource : Employee
        where TTarget : Employee, new()
    {
        var target = new TTarget();
        // 获取源对象的可读公共属性
        var sourceProps = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .Where(p => p.CanRead && p.GetIndexParameters().Length == 0);
        // 获取目标对象的可初始化公共属性
        var targetProps = typeof(TTarget).GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .Where(p => p.CanWrite && p.GetIndexParameters().Length == 0 && p.SetMethod?.IsInitOnly == true);

        foreach (var sourceProp in sourceProps)
        {
            var targetProp = targetProps.FirstOrDefault(p => p.Name == sourceProp.Name && p.PropertyType == sourceProp.PropertyType);
            if (targetProp != null)
            {
                targetProp.SetValue(target, sourceProp.GetValue(source));
            }
        }

        return target;
    }
}

调用示例:

var trainee = new Trainee { EmployeeId = 123, ManagerId = 456 };
// 复制基类属性后补充Manager独有的字段
var manager = RecordConverter.Convert<Trainee, Manager>(trainee) with { AdminCode = "ADMIN001" };

一次封装通用逻辑,所有同基类的record都能复用,适合字段多的场景。

方法3:序列化反序列化中转

如果本来就在处理JSON序列化流程,可以直接用序列化库中转,自动复制所有可序列化的属性:

using System.Text.Json;

public static class RecordConverter
{
    public static TTarget ConvertViaJson<TSource, TTarget>(TSource source)
        where TSource : Employee
        where TTarget : Employee
    {
        var json = JsonSerializer.Serialize(source);
        return JsonSerializer.Deserialize<TTarget>(json)!;
    }
}

调用示例:

var trainee = new Trainee { EmployeeId = 123, ManagerId = 456 };
var manager = RecordConverter.ConvertViaJson<Trainee, Manager>(trainee) with { AdminCode = "ADMIN001" };

优点是不用关心字段变化,缺点是有序列化性能开销,适合对性能要求不高的场景。

方法4:.NET 8+ 表达式树高性能映射

如果需要极致性能,用表达式树提前编译映射委托,避免反射的性能损耗:

using System.Linq.Expressions;

public static class RecordConverter
{
    private static readonly Dictionary<Type, Dictionary<Type, Delegate>> _cachedConverters = new();

    public static TTarget Convert<TSource, TTarget>(TSource source)
        where TSource : Employee
        where TTarget : Employee, new()
    {
        var sourceType = typeof(TSource);
        var targetType = typeof(TTarget);

        if (!_cachedConverters.TryGetValue(sourceType, out var targetMap) || !targetMap.TryGetValue(targetType, out var converter))
        {
            converter = BuildConverter<TSource, TTarget>();
            _cachedConverters.TryAdd(sourceType, new Dictionary<Type, Delegate>());
            _cachedConverters[sourceType][targetType] = converter;
        }

        return ((Func<TSource, TTarget>)converter)(source);
    }

    private static Func<TSource, TTarget> BuildConverter<TSource, TTarget>()
        where TSource : Employee
        where TTarget : Employee, new()
    {
        var sourceParam = Expression.Parameter(typeof(TSource), "source");
        var targetCtor = Expression.New(typeof(TTarget));

        var bindings = new List<MemberBinding>();
        var sourceProps = typeof(TSource).GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .Where(p => p.CanRead && p.GetIndexParameters().Length == 0);
        var targetProps = typeof(TTarget).GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .Where(p => p.CanWrite && p.GetIndexParameters().Length == 0 && p.SetMethod?.IsInitOnly == true);

        foreach (var sourceProp in sourceProps)
        {
            var targetProp = targetProps.FirstOrDefault(p => p.Name == sourceProp.Name && p.PropertyType == sourceProp.PropertyType);
            if (targetProp != null)
            {
                bindings.Add(Expression.Bind(targetProp, Expression.Property(sourceParam, sourceProp)));
            }
        }

        var initExpr = Expression.MemberInit(targetCtor, bindings);
        return Expression.Lambda<Func<TSource, TTarget>>(initExpr, sourceParam).Compile();
    }
}

第一次调用会编译映射委托,后续调用性能接近手动写的映射,适合高性能场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 15:40:12