向上投,然后向下投下记录 C#

Upcast and then downcast a record C#

提问人:AceGambit 提问时间:6/6/2023 最后编辑:EnigmativityAceGambit 更新时间:6/6/2023 访问量:103

问:

想象一下以下记录类

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; }
}

现在,假设我有一个受训对象,我想从中创建管理器对象。当然,我可以一次绘制一个字段,但在现实生活中,我有更大的对象。如果这些是类,这将是一个不切实际的,但是使用简洁的语法,例如,我们可以做一些很酷的事情来在 C# 中创建记录的副本。with

我想做什么,但不知道该怎么做(pesudo代码):

private Boss PromoteTrainee(Trainee trainee, string adminCode)
{
  return (trainee as Employee as Manager) with { AdminCode = adminCode };
}

这当然是行不通的,因为第二次强制转换失败并出现 null。有没有办法做到这一点,不需要手动复制 EmployeeId(以及我的基本对象上可能存在的所有其他字段)?as

**编辑:我认为具有误导性的例子**

我现实生活中的例子是,我有一堆 json 合约和 DTO 对象。许多对象共享大量相同的字段(这些字段不会更改,但如果更改,它们总是会一起更改),因此我创建了一个基本记录来保存这些字段,这样我就不必在对象之间复制它们。

我有因为我更喜欢 DTO 的不可变对象,所以我使用带有 on 属性的类型而不是 .但是,这限制了我的代码,只能在创建时使用对象初始值设定项(或具有其他记录语法的构造函数)设置属性。recordinitset

因此,如果我已经有了创建包含所有受训者字段和员工基本字段的受训者 DTO 的逻辑,那么我如何为经理 DTO 重用该设置逻辑,更改设置经理 DTO 字段的部分,而无需复制大量代码?

C# .NET 强制转换

评论

3赞 madreflection 6/6/2023
继承是用例的错误设计决策。无论他们是经理、实习生,还是任何贴在人身上的标签(或财产)。你不会在某人被提拔时杀死并重新长出一个新人(除非,也许,他们是 Vorta),但这就是你在这里建模的。
3赞 madreflection 6/6/2023
也许你在想“但这个员工是经理”,但实际上这个员工“经理的经验和责任水平”。
0赞 NetMage 6/6/2023
为什么你的例子而不是?您似乎没有使用 .recordclassrecord
0赞 AceGambit 6/6/2023
看编辑 -- 这些评论表明我的例子过于简单化,不能有效地代表我的现实生活情况。
1赞 NetMage 6/6/2023
我认为您应该将位置参数用于主构造函数,它将自动声明相应的位置属性。

答:

1赞 AceGambit 6/6/2023 #1

我找到了一个“有效”的解决方案,尽管它可能有些骇人听闻,但它确实解决了我的问题。

public record Manager : Employee
{
    public Manager(Employee fromBase)
    {
        var props = typeof(Employee).GetProperties();
        foreach (var propertyInfo in props)
        {
            propertyInfo.SetValue(this, 
               propertyInfo.GetValue(fromBase));
        }
    }

    public string AdminCode { get; init; }
}

这允许构造 Trainee(如果抽象不是必需的,则为 Employee)的实例,并使用它来构造 Manager 的实例,并复制所有属性,如下所示:

var manager = new Manager(trainee){ AdminCode = "1234" };

评论

0赞 NetMage 6/13/2023
如果你打算这样做,我建议至少将 缓存在一个静态变量中,这样你就不必在每次使用它时都运行它。propsGetProperties
1赞 NetMage 6/6/2023 #2

如果您使用的是 ,则应利用位置参数和主要构造函数:record

public abstract record Employee(int EmployeeId) {
}

public record Manager(int EmployeeID, string AdminCode) : Employee(EmployeeID) {
}

public record Trainee(int EmployeeId, int Managerid) : Employee(EmployeeId) {
}

private Manager PromoteTrainee(Trainee trainee, string adminCode)
    => new Manager(trainee.EmployeeId, adminCode);

如果您有很多属性,并且想要避免复制它们,也许不是正确的解决方案。your 和 可以共享接口或具有包含它所描述的 的属性。EmployeerecordManagerTraineeEmployeeEmployee

0赞 Enigmativity 6/6/2023 #3

作曲是你可以走的路吗?

试试这个:

public interface IEmployee
{
    int EmployeeId { get; }
}

public record Employee : IEmployee
{
    public int EmployeeId { get; init; }
}

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

    private IEmployee _employee;

    public Manager(IEmployee employee)
    {
        _employee = employee;
    }
    
    public int EmployeeId => _employee.EmployeeId;

    public Trainee Demote(int managerId) =>
        new Trainee(_employee) { ManagerId = managerId};
}

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

    private IEmployee _employee;

    public Trainee(IEmployee employee)
    {
        _employee = employee;
    }

    public int EmployeeId => _employee.EmployeeId;

    public Manager Promote(string adminCode) =>
        new Manager(_employee) { AdminCode = adminCode };
}

那么你的代码几乎和你建议的伪代码一模一样:

Trainee trainee = new Trainee(new Employee() { EmployeeId = 42 })
{
    ManagerId = 101
};
Manager manager = trainee.Promote("AdminCodeHere");

评论

0赞 AceGambit 6/6/2023
我很欣赏这里的创造力,但这种实现需要在 Manager 和 Trainee 类中明确列出 Employee 的每个字段