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.