MSDN.WhiteKnight - Stack Overflow answers
Ответ на "FluentBuilder для immutable класса"
Answer 1216199
А хотелось бы в результате как-то так...
Я бы покритиковал сам постановку задачи.
В чем смысл PersonBuilder? Это какая-то искусственная сущность, по сути мутабельный двойник иммутабельного класса, который нужно дублировать для каждого такого класса. Не лучше ли строить объекты унифицированным способом?
.Set(p => p.FirstName, "Андрей")
- использование лямбда-выражения ни с того, ни с сего, хотя анонимная функция тут вообще не вызывается. Почему не.Set("FirstName", "Андрей")
?
.Include(p => p)
- вообще не понятно, что такое. Как включить самого себя? По логике, происходит переход к родительскому объекту, так что лучше бы создать отдельный метод "ToParent"Я бы сделал это так. Допустим, мы принимаем соглашение, что у иммутабельного класса имена get-only свойств начинаются с большой буквы. Параметры конструктора соответствуют по типу его свойствам, а имя параметра начинается путем замены первой буквы имени свойства на такую же маленькую букву. Создадим такой класс:
public class ImmutableEntity { static Dictionary<Type, Delegate[]> gettersCache = new Dictionary<Type, Delegate[]>(); static Dictionary<Type, Delegate> constrCache = new Dictionary<Type, Delegate>(); ImmutableEntity parent;//родительский объект, если этот объект - значение свойства другого объекта string parentProperty;//свойство родительского объекта Delegate[] getters; Delegate constr; protected ImmutableEntity() { if (gettersCache.ContainsKey(this.GetType())) { constr = constrCache[this.GetType()]; getters = gettersCache[this.GetType()]; } else { PropertyInfo p; //получаем первый конструктор, имеющий параметры ConstructorInfo ci = this.GetType().GetConstructors(). Where(x => x.GetParameters().Length > 0).First(); ParameterInfo[] p_arr; p_arr = ci.GetParameters(); //получаем массив делегатов для получения значений свойств ParameterExpression[] expressions = new ParameterExpression[p_arr.Length]; this.getters = new Delegate[p_arr.Length]; for (int i = 0; i < p_arr.Length; i++) { //получаем имя свойства string propname = p_arr[i].Name; propname = Char.ToUpper(propname[0]).ToString() + propname.Substring(1); //создаем делегат p = this.GetType().GetProperty( propname, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ); MethodInfo mb = p.GetGetMethod(); Type delegateType = typeof(Func<,>).MakeGenericType(this.GetType(), p.PropertyType); getters[i] = mb.CreateDelegate(delegateType); //создаем выражение для параметра expressions[i] = Expression.Parameter(p.PropertyType); } //создаем делегат для вызова конструктора LambdaExpression expr = Expression.Lambda(Expression.New(ci, expressions), expressions); constr = expr.Compile(); //сохраняем делегаты, чтобы не пересоздавать для каждого объекта одного типа gettersCache[this.GetType()] = getters; constrCache[this.GetType()] = constr; } } public ImmutableEntity Set(string prop, object val) { //получаем имя параметра из имени свойства string pname = prop; pname = Char.ToLower(pname[0]).ToString() + pname.Substring(1); //получаем первый конструктор, имеющий параметры ConstructorInfo ci = this.GetType().GetConstructors(). Where(x => x.GetParameters().Length > 0).First(); //формируем массив значений параметров ParameterInfo[] p_arr; p_arr = ci.GetParameters(); object[] pars = new object[p_arr.Length]; for (int i = 0; i < p_arr.Length; i++) { if (p_arr[i].Name == pname) { //свойство, которое изменилось pars[i] = val; } else { pars[i] = this.getters[i].DynamicInvoke(this); } } //создаем измененный объект ImmutableEntity ret=(ImmutableEntity)constr.DynamicInvoke(pars); ret.parent = this.parent; ret.parentProperty = this.parentProperty; return ret; } public static object GetDefault(Type t) { if (t.IsValueType) return Activator.CreateInstance(t); else return null; } public ImmutableEntity Include(string prop) { //получаем имя параметра из имени свойства string pname = prop; pname = Char.ToLower(pname[0]).ToString() + pname.Substring(1); //получаем первый конструктор, имеющий параметры ConstructorInfo ci = this.GetType().GetConstructors(). Where(x => x.GetParameters().Length > 0).First(); //формируем массив значений параметров ParameterInfo[] p_arr; p_arr = ci.GetParameters(); object[] pars = new object[p_arr.Length]; ImmutableEntity included =null; for (int i = 0; i < p_arr.Length; i++) { if (p_arr[i].Name == pname) { //свойство для включения included = this.getters[i].DynamicInvoke(this) as ImmutableEntity; if (included == null) { included = New(p_arr[i].ParameterType); included.parentProperty = prop; } pars[i] = included; } else { pars[i] = this.getters[i].DynamicInvoke(this); } } //включаем объект в родительский included.parent = (ImmutableEntity)constr.DynamicInvoke(pars); return included; } public ImmutableEntity ToParent() { if (this.parent == null) return null; return this.parent.Set(this.parentProperty, this); } public static T New<T>() where T:ImmutableEntity { return (T)New(typeof(T)); } public static ImmutableEntity New(Type t) { //создаем пустой объект указанного типа ConstructorInfo ci = t.GetConstructors(). Where(x => x.GetParameters().Length > 0).First(); ParameterInfo[] p_arr = ci.GetParameters(); object[] pars = new object[p_arr.Length]; for (int i = 0; i < p_arr.Length; i++) { pars[i] = GetDefault(p_arr[i].ParameterType); } return (ImmutableEntity)ci.Invoke(pars); } }
Его суть в использовании выражений для вызова конструктора объекта и получения значений свойств. Так можно строить любой объект, который удовлетворяет нашим соглашениям.
Сделаем иммутабельные классы производными от него:
public class Person: ImmutableEntity { 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}"; } } public class Address : ImmutableEntity { public Address(string city, int postCode) { this.City = city; this.PostCode = postCode; } public string City { get; } public int PostCode { get; } public override string ToString() { return $"Адрес: {City}-{PostCode} "; } } public class Employment : ImmutableEntity { public Employment(string companyName, string position,int annualIncome) { this.CompanyName = companyName; this.Position = position; this.AnnualIncome = annualIncome; } public string CompanyName { get; } public string Position { get; } public int AnnualIncome { get; } public override string ToString() { return $"Работа: {CompanyName}, должность: {Position}, зарплата: {AnnualIncome}"; } }
Тогда использовать можно так:
var person = (Person)ImmutableEntity.New<Person>(). Set(nameof(Person.FirstName), "Ivan"). Set(nameof(Person.LastName), "Ivanov"). Include(nameof(Person.Address)). Set(nameof(Address.City), "Chelyabinsk"). ToParent(). Include(nameof(Person.Employment)). Set(nameof(Employment.Position), "Engineer"). ToParent();
Content is retrieved from StackExchange API.
Auto-generated by ruso-archive tools.