FluentBuilder для immutable класса

1,00
р.
В общем по следам этого вопроса решил написать примерчик, но к сожалению не осилил 😢. Может кто из вас поможет дописать 😉.
Имеем такой класс человека
class Person { public Person(string firstName, string lastName, Address address, Employment employment) { FirstName = firstName LastName = lastName Address = address Employment = employment }
public string FirstName { get } public string LastName { get } public Address Address { get } public Employment Employment { get }
public override string ToString() { return $"{FirstName} {LastName}: " + Environment.NewLine + $"{Address}" + Environment.NewLine + $"{Employment}" } }
с сопутствующими классами
class Address { public string City { get set } public int PostCode { get set }
public override string ToString() { return $"Адрес: {City}-{PostCode} " } }
class Employment { public string CompanyName { get set } public string Position { get set } public int AnnualIncome { get set }
public override string ToString() { return $"Работа: {CompanyName}, должность: {Position}, зарплата: {AnnualIncome}" } }
Самостоятельно удалось написать только такой строитель
class PersonBuilder { //аккумулятор названий свойств и их значений private readonly Dictionary _propertiesToBuild
public PersonBuilder() { _propertiesToBuild = new Dictionary() }
//запоминание в словаре названия свойства и его значения public PersonBuilder Set(Expression> expression, T value) { var propertyName = ((MemberExpression)expression.Body).Member.Name _propertiesToBuild.Add(propertyName, value) return this }
public T Include(Expression> expression) { var propertyName = ((MemberExpression)expression.Body).Member.Name if (propertyName == nameof(Person.Address)) { var result = new Address() return (T)(object)result } else { var result = new Employment() return (T)(object)result } }
//создание экземпляра на основе значений свойств из словаря public Person Build() { return new Person ( firstName: GetPropertyValue(nameof(Person.FirstName), "Неизвестно"), lastName: GetPropertyValue(nameof(Person.LastName), "Неизвестно"), address: new Address(), employment: new Employment() ) }
private T GetPropertyValue(string propertyName, T defaultValue) { return _propertiesToBuild.TryGetValue(propertyName, out var value) ? (T)value : defaultValue } }
И пока это работает так
var person = new PersonBuilder() .Set(p => p.FirstName, "Андрей") .Set(p => p.LastName, "Иванов") .Build()
или так
Address address = new PersonBuilder() .Set(p => p.FirstName, "Андрей") .Set(p => p.LastName, "Иванов") .Include(p => p.Address)
ну или так
Employment employment = new PersonBuilder() .Set(p => p.FirstName, "Андрей") .Set(p => p.LastName, "Иванов") .Include(p => p.Employment)
А хотелось бы в результате как-то так
Person person = new PersonBuilder() .Set(p => p.FirstName, "Андрей") .Set(p => p.LastName, "Иванов") .Include(p => p.Address) .Set(a => a.City, "Москва") .Include(p => p) .Include(p => p.Employment) .Set(e => e.Position, "инженер") .Include(p => p) .Build()
P.S. получившейся в итоге вариант здесь, спасибо @Vasek

Ответ
В качестве пропаганды всего нового и хорошего напомню, что immutable + builder из коробки поддерживается 9-ой версией C#.
Добавляем в .csproj 9.0, и ваш пример можно магически записать следующим образом:
record Person { public string FirstName { get init } public string LastName { get init } public Address Address { get init } public Employment Employment { get init } public override string ToString() => $"{FirstName} {LastName}:
{Address}
{Employment}" }
record Address { public string City { get init } public int PostCode { get init } public override string ToString() => $"Адрес: {City}-{PostCode}" }
record Employment { public string CompanyName { get init } public string Position { get init } public int AnnualIncome { get init } public override string ToString() => $"Работа: {CompanyName}, должность: {Position}, зарплата: {AnnualIncome}" }
А код, использующий всё это, запишется так:
var person = new Person() with { FirstName = "Андрей" } with { LastName = "Иванов" } with { Address = new Address() with { City = "Мюнхен" } with { PostCode = 80000 } } with { Employment = new Employment() with { CompanyName = "Google" } with { Position = "CEO" } with { AnnualIncome = 100500 } }
Всё!