Upcast and then downcast a record C#.

huangapple go评论64阅读模式
英文:

Upcast and then downcast a record C#

问题

以下是要翻译的代码部分:

想象一下以下的记录类

```C#
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对象。当然,我可以逐个映射字段,但在实际应用中,我有 非常 大的对象。如果这些是类的话,这将是一个不可行的方法,但是有了像 with 这样的漂亮语法,我们可以在C#中创建记录的副本。

我想要做的事情,但不知道如何实现(伪代码):

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

当然,这不起作用,因为第二个 as 转换失败并返回 null。有没有一种方法可以做到这一点,而不需要手动复制EmployeeId(以及可能在我的基础对象上存在的所有其他字段)?

编辑:我认为这个示例有误

我的真实例子是,我有一堆JSON合同和DTO对象。许多对象共享大量相同的字段(这些字段不会更改,但如果它们更改,它们将始终一起更改),因此我创建了一个基础记录以保存这些字段,以便我不必在这些对象之间重复它们。

由于我更喜欢DTO的不可变对象,我在属性上使用了 init,而不是 set。但是,这限制了我的代码只能在创建时使用对象初始化程序(或使用其他记录语法的构造函数)设置属性。

因此,如果我已经有了创建具有所有Trainee字段和Employee基本字段的Trainee DTO的逻辑,那么如何重复使用该设置逻辑以创建一个Manager DTO,同时更改设置Manager DTO字段的部分,而不重复大量代码?


<details>
<summary>英文:</summary>

Imagine the following record classes

```C#
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; }
}

Now, assume I have a trainee, object that I'd like to create a manager object out of. Of course I can map the fields one at a time, but in real life, I have much bigger objects. If these were classes this would be a non-starter but with neat syntax like with, we can do some cool stuff to create copies of records in C#.

What I want to do, but don't know how to (pesudo-code):

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

This of course does not work because the second as cast fails and comes up with null. Is there a way to do this which doesn't require manually copying the EmployeeId (and all the other fields that might be on my base object)?

**EDIT: Misleading example I think **

My real life example is that I have a bunch of json contracts and DTO objects for them. Many of the objects share a TON of the same fields (which don't change but if they did, they'd always change together), so I created a base record to hold those fields so I don't have to duplicate them across the objects.

I've because I prefer immutable objects for DTOs, I use record types with init on the properties instead of set. This however limits my code to only be able to set the properties at creation time using object initializers (or constructors with the other record syntax).

So if I've already got the logic to create a Trainee DTO with all of their trainee fields and Employee base fields, how can I reuse that setup logic for a Manager DTO changing the parts that set the Manager DTO field without duplicating a ton of code?

答案1

得分: 1

我找到了一个“可行”的解决方案,尽管它可能有点巧妙,但确实解决了我的问题。

```C#
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" };
英文:

I've found a solution that "works" though it might be somewhat hacky, it does solve my problem.

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

This allows to construct an instance of Trainee (or Employee if abstract isn't a requirement), and use it to construct an instance of Manager with all the properties copied like so:

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

答案2

得分: 1

如果您正在使用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);

如果Employee中有许多属性,并且希望避免它们之间的复制,也许record不是正确的解决方案。您的ManagerTrainee可以共享一个包含它所描述的Employee的接口或属性。

英文:

If you are using record, you should take advantage of positional parameters and primary constructors:

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)
    =&gt; new Manager(trainee.EmployeeId, adminCode);

If you have a lot of properties in Employee, and want to avoid copying them around, perhaps record isn't the right solution. Your Manager and Trainee could share an interface with Employee or have a property that contains the Employee that it describes.

答案3

得分: 0

Composition是一种可行的方法吗?

尝试这个:

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

然后你的代码几乎与你提出的伪代码完全相同:

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

Is composition a way you can go?

Try this:

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 =&gt; _employee.EmployeeId;

	public Trainee Demote(int managerId) =&gt;
		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 =&gt; _employee.EmployeeId;

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

Then your code is almost exactly like the pseudo-code you suggested:

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

huangapple
  • 本文由 发表于 2023年6月6日 04:32:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/76409812.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定