Parents aus Childs erzeugen - mit Records
Einer der von mir am häufigsten selbst gelesene Blog-Artikel ist Child-Objekt aus Parents erzeugen - mit Records. Heute stand ich vor der umgekehrten Herausforderung. Ich wollte aus dem Child-Objekt ein Parent-Objekt machen. Eigentlich eine einfache Aufgabe sollten man meinen. Nehmen wir wieder diese Klassenhierarchie
public record InputDto {
public string Name {get;set;}
public int Alter {get;set;}
}
public record InputDtoWithId : InputDto {
public int Id {get;set;}
}
Ich habe eine List<InputDtoWithId>
, die ich in eine List<InputDto>
verwandeln möchte, um sie dann in einer Cosmos-DB zu speichern. Der nachfolgende Code zeigt die ersten beiden Ansätze:
List<InputDtoWithId> childList = new List<InputDtoWithId> {
new InputDtoWithId {Id = 1, Name = "Hans", Alter = 10},
new InputDtoWithId {Id = 2, Name = "Peter", Alter = 20},
};
//enthält dann immer noch die InputDtoWithId-Objekte, die nur in eine andere Liste verpackt werden.
List<InputDto> parentList = childList.Select(c => (InputDto) c).ToList();
//zweite Idee, Nutzung des Record-Copy-Constructors
List<InputDto> parentList2 = childList.Select(c => ((InputDto) c) with {} ).ToList();
Dass der erste Ansatz nicht hilft, war zu erwarten. Dass aber auch in parentList2
immer noch InputDtoWithId-Objekte stehen, hat mich überrascht. Für meinen Use-Case war das ein Problem, weil ich das Objekt als JSON speichern wollte, aber explizit OHNE die Werte der Child-Klasse.
Des Rästels Lösung habe ich in Thomas Claudius Hubers Blog gefunden, der eine tolle Erklärung für Records geschrieben hat. Die With-Expression nutzt die Clone-Methode des eigentlichen Objekts und nicht die der Base-Klasse.
Die Lösung ist ziemlich hacky, aber für meinen Anwendungsfall gut genug, so dass ich auch eine Zirkelbeziehung zwischen Parent- and Child-Klasse tolleriere. Dazu wird die InputDto
-Klasse um zwei Konstruktoren erweitert:
//muss explizit angegeben werden, wenn ein Konstruktor mit Parametern existiert, sofern man den leeren Konstruktor benötigt.
public InputDto() {}
/// <summary>
/// Eigener Copy-Constructor, um Objekte in die Basisklasse konvertieren zu können. /// </summary>
/// <example>
/// new InputDto(dto);
/// </example>
public InputDto(InputDtoWithId dto) : this((InputDto) dto)
{}
Der Trick besteht im Aufruf des Copy-Konstruktors und dem explizitem Cast des Child-Objekts in die Parent-Klasse.