实例化不可变对象与反思 [英] Instantiating Immutable Objects With Reflection
问题描述
我创建了一个基类来帮助我减少在C#中的不可变对象的初始化样板代码,
我为了尽量不使用延迟初始化?通过这样做是为了影响性能提升不少,
我不知道有多少我会影响性能。
这是我的基类:
公共类ImmutableObject< T>
{
私人只读Func键<&IEnumerable的LT; KeyValuePair<字符串对象>>> initContainer;
保护ImmutableObject(){}
保护ImmutableObject(IEnumerable的&所述; KeyValuePair&下;串,对象>>属性)
{
变种字段=的GetType ().GetFields()式(F => f.IsPublic);
变种fieldsAndValues =
从字段信息领域中的
加入keyValuePair在性能上fieldInfo.Name.ToLower()等于keyValuePair.Key.ToLower()
选择新{字段信息,keyValuePair.Value};
fieldsAndValues.ToList()的ForEach(FV => fv.fieldInfo.SetValue(这一点,fv.Value));
}
保护ImmutableObject(Func键< IEnumerable的< KeyValuePair<字符串对象>>>初始化)
{
initContainer = INIT;
}
保护牛逼的setProperty(字符串propertyName的,对象为PropertyValue,布尔懒= TRUE)
{
Func键< IEnumerable的< KeyValuePair<字符串对象> ;>> mergeFunc =委托
{
VAR propertyDict = initContainer == NULL? ObjectToDictonary():initContainer();
返回propertyDict.Select(P => p.Key == propertyName的新KeyValuePair<字符串对象>(propertyName的,为PropertyValue):P).ToList();
};
VAR containerConstructor = typeof运算(T).GetConstructors()
。首先(CE => ce.GetParameters()计数()== 1安培;&安培; ce.GetParameters( )[0] .ParameterType.Name ==Func`1);
回报率(T)(懒containerConstructor.Invoke(新[] {} mergeFunc):DictonaryToObject< T>(mergeFunc()));
}
私人的IEnumerable< KeyValuePair<字符串对象>> ObjectToDictonary()
{
VAR栏=的GetType()GetFields()式(F => f.IsPublic)。
返回fields.Select(F =>新建KeyValuePair<字符串对象>(f.Name,f.GetValue(本)))。了ToList();
}
私有静态对象DictonaryToObject< T>(IEnumerable的< KeyValuePair<字符串对象>> objectProperties)
{
VAR mainConstructor = typeof运算(T)。 GetConstructors()
。首先(C => c.GetParameters()计数()== 1和;&放大器; c.GetParameters()任何(p值=方式> p.ParameterType.Name == IEnumerable`1));
返回mainConstructor.Invoke(新[] {objectProperties});
}
公共牛逼ToObject()
{
VAR性能= initContainer == NULL? ObjectToDictonary():initContainer();
回报率(T)DictonaryToObject< T>(属性); $:
}
}
可以像这样实现的b
$ b
公共类国家:ImmutableObject<州与GT;
{
公共状态(){}
公共状态(IEnumerable的&所述; KeyValuePair&下;串,对象>>属性):基座(属性){}
公共状态(Func键&下; IEnumerable的< KeyValuePair<字符串对象>>> FUNC):基地(FUNC){}
公共只读INT SomeInt;
公州someInt(INT someInt)
{
返回的setProperty(SomeInt,someInt);
}
公共只读字符串SomeString;
公州someString(字符串someString)
{
返回的setProperty(SomeStringsomeString);
}
}
和可以使用这样的:
//创建新的空对象
变种状态=新的国家();
//设置字段,将返回一个空的对象与链方法。
VAR S2 = state.someInt(3).someString(字符串);
//解析所有的链接的方法,并初始化对象反射设置的所有字段。
变种S3 = s2.ToObject();
,它将使更有意义,而不是混为一谈永恒不变的情况下实现或接口的本质上是一个制造商作为新实例的行为。
您可以做一个更清洁,很类型安全的解决方案的方式。因此,我们可以定义一些标记的接口和类型安全的版本上。
public接口IImmutable:ICloneable {}
公共接口IImmutableBuilder {}
公共接口IImmutableOf< T> :IImmutable其中T:类,IImmutable
{
IImmutableBuilderFor< T>突变();
}
公共接口IImmutableBuilderFor< T> :IImmutableBuilder其中T:类,IImmutable
{$ B $(B T)来源{搞定; }
IImmutableBuilderFor< T> SET< TFieldType>(字符串fieldName的,TFieldType值);
IImmutableBuilderFor< T> SET< TFieldType>(字符串字段名,Func键< T,TFieldType> valueProvider);
IImmutableBuilderFor< T> SET< TFieldType>(表达式来; Func键< T,TFieldType>> fieldExpression,TFieldType值);
IImmutableBuilderFor< T> SET< TFieldType>(表达式来; Func键< T,TFieldType>> fieldExpression,Func键< TFieldType,TFieldType> valueProvider); $ B $(B T)构建();
}
和提供所有需要的基本的建设者行为一类像下面。注意,大多数的错误检查/编译委托创建是为简洁起见省略/简化起见。错误检查的合理水平一个更清洁,性能优化的版本可以在此要点被发现。
公共类DefaultBuilderFor< T> :IImmutableBuilderFor< T>其中T:类,IImmutableOf< T>
{
私人静态只读的IDictionary<字符串,元组LT;类型,动作< T,对象>>> _setters;
私有列表<作用< T>> _mutations =新的List<作用< T>>();
静态DefaultBuilderFor()
{
_setters = GetFieldSetters();
}
公共DefaultBuilderFor(T实例)
{
来源=实例;
}
公共牛逼来源{搞定;私人集; }
公共IImmutableBuilderFor< T> SET< TFieldType>(字符串fieldName的,TFieldType值)
{
//注意:检查省略&放错误;加入到做什么,如果`TFieldType`不是正确的。
_mutations.Add(研究所= GT; _setters [字段名] .Item2(研究所,值));
返回这一点;
}
公共IImmutableBuilderFor< T> SET< TFieldType>(字符串字段名,Func键< T,TFieldType> valueProvider)
{
//注意:检查省略&放错误;加入到做什么,如果`TFieldType`不是正确的。
_mutations.Add(研究所= GT; _setters [fieldName的] .Item2(研究所,valueProvider(研究所)));
返回这一点;
}
公共IImmutableBuilderFor< T> SET< TFieldType>(表达式来; Func键< T,TFieldType>> fieldExpression,TFieldType值)
{
检查省略//错误。
VAR memberExpression = fieldExpression.Body为MemberExpression;
返回SET< TFieldType>(memberExpression.Member.Name,值);
}
公共IImmutableBuilderFor< T> SET< TFieldType>(表达式来; Func键< T,TFieldType>> fieldExpression,Func键< TFieldType,TFieldType> valueProvider)检查省略
{
//错误。
VAR memberExpression = fieldExpression.Body为MemberExpression;
VAR吸气= fieldExpression.Compile();
返回SET< TFieldType>(memberExpression.Member.Name,研究所= GT; valueProvider(吸气剂(研究所)));
}
公共牛逼生成()
{
VAR的结果=(T)Source.Clone();
_mutations.ForEach(X => X(结果));
返回结果;
}
私有静态的IDictionary<字符串,元组LT;类型,动作< T,对象>>> GetFieldSetters()
{
//注:可使用委托创作二传手(IL)进行优化。
返回的typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance)
。凡(X =>!x.IsLiteral)
.ToDictionary(
X => x.Name,
X = GT; SetterEntry(x.FieldType,(研究所,VAL)=> x.SetValue(研究所,VAL)));
}
私有静态元组LT;类型,动作< T,对象>> SetterEntry(类型类型,动作< T,对象>的setter)
{
返回Tuple.Create(类型,二传手);
}
}
示例使用
这然后可以这样使用,使用示例类国家
的:
公共静态类示例
{
公共类国家:IImmutableOf<州与GT;
{
公国(INT someInt,字符串someString)
{
SomeInt = someInt;
SomeString = someString;
}
公共只读INT SomeInt;
公共只读字符串SomeString;
公共IImmutableBuilderFor<州与GT;变异()
{
返回新DefaultBuilderFor<州及GT;(本);
}
公共对象的clone()
{
返回base.MemberwiseClone();
}
公共重写字符串的ToString()
{
返回的String.Format({0},{1},SomeInt,SomeString);
}
}
公共静态无效的run()
{
变种原=新的国家(10,初始);
VAR mutatedInstance = original.Mutate()
。设置(SomeInt,45)
。设置(X => x.SomeString,你好SO)
.Build();
Console.WriteLine(mutatedInstance);
mutatedInstance = original.Mutate()
。设置(X => x.SomeInt,VAL => VAL + 10)
.Build();
Console.WriteLine(mutatedInstance);
}
}
通过以下的输出:
45,你好SO
20,初始
I created a base class to help me reduce boilerplate code of the initialization of the immutable Objects in C#,
I'm using lazy initialization in order to try not to impact performance a lot , I was wondering how much am I affecting the performance by doing this?
This is my base class:
public class ImmutableObject<T>
{
private readonly Func<IEnumerable<KeyValuePair<string, object>>> initContainer;
protected ImmutableObject() {}
protected ImmutableObject(IEnumerable<KeyValuePair<string,object>> properties)
{
var fields = GetType().GetFields().Where(f=> f.IsPublic);
var fieldsAndValues =
from fieldInfo in fields
join keyValuePair in properties on fieldInfo.Name.ToLower() equals keyValuePair.Key.ToLower()
select new {fieldInfo, keyValuePair.Value};
fieldsAndValues.ToList().ForEach(fv=> fv.fieldInfo.SetValue(this,fv.Value));
}
protected ImmutableObject(Func<IEnumerable<KeyValuePair<string,object>>> init)
{
initContainer = init;
}
protected T setProperty(string propertyName, object propertyValue, bool lazy = true)
{
Func<IEnumerable<KeyValuePair<string, object>>> mergeFunc = delegate
{
var propertyDict = initContainer == null ? ObjectToDictonary () : initContainer();
return propertyDict.Select(p => p.Key == propertyName? new KeyValuePair<string, object>(propertyName, propertyValue) : p).ToList();
};
var containerConstructor = typeof(T).GetConstructors()
.First( ce => ce.GetParameters().Count() == 1 && ce.GetParameters()[0].ParameterType.Name == "Func`1");
return (T) (lazy ? containerConstructor.Invoke(new[] {mergeFunc}) : DictonaryToObject<T>(mergeFunc()));
}
private IEnumerable<KeyValuePair<string,object>> ObjectToDictonary()
{
var fields = GetType().GetFields().Where(f=> f.IsPublic);
return fields.Select(f=> new KeyValuePair<string,object>(f.Name, f.GetValue(this))).ToList();
}
private static object DictonaryToObject<T>(IEnumerable<KeyValuePair<string,object>> objectProperties)
{
var mainConstructor = typeof (T).GetConstructors()
.First(c => c.GetParameters().Count()== 1 && c.GetParameters().Any(p => p.ParameterType.Name == "IEnumerable`1") );
return mainConstructor.Invoke(new[]{objectProperties});
}
public T ToObject()
{
var properties = initContainer == null ? ObjectToDictonary() : initContainer();
return (T) DictonaryToObject<T>(properties);
}
}
Can be implemented like so:
public class State:ImmutableObject<State>
{
public State(){}
public State(IEnumerable<KeyValuePair<string,object>> properties):base(properties) {}
public State(Func<IEnumerable<KeyValuePair<string, object>>> func):base(func) {}
public readonly int SomeInt;
public State someInt(int someInt)
{
return setProperty("SomeInt", someInt);
}
public readonly string SomeString;
public State someString(string someString)
{
return setProperty("SomeString", someString);
}
}
and can be used like this:
//creating new empty object
var state = new State();
// Set fields, will return an empty object with the "chained methods".
var s2 = state.someInt(3).someString("a string");
// Resolves all the "chained methods" and initialize the object setting all the fields by reflection.
var s3 = s2.ToObject();
As was already mentioned in the comments, it would make more sense, not to "conflate" the immutable instance implementation or interface with the behavior of what is essentially a builder for new instances.
You could make a much cleaner and quite type safe solution that way. So we could define some marker interfaces and type safe versions thereof:
public interface IImmutable : ICloneable { }
public interface IImmutableBuilder { }
public interface IImmutableOf<T> : IImmutable where T : class, IImmutable
{
IImmutableBuilderFor<T> Mutate();
}
public interface IImmutableBuilderFor<T> : IImmutableBuilder where T : class, IImmutable
{
T Source { get; }
IImmutableBuilderFor<T> Set<TFieldType>(string fieldName, TFieldType value);
IImmutableBuilderFor<T> Set<TFieldType>(string fieldName, Func<T, TFieldType> valueProvider);
IImmutableBuilderFor<T> Set<TFieldType>(Expression<Func<T, TFieldType>> fieldExpression, TFieldType value);
IImmutableBuilderFor<T> Set<TFieldType>(Expression<Func<T, TFieldType>> fieldExpression, Func<TFieldType, TFieldType> valueProvider);
T Build();
}
And provide all the required basic builder behavior in a class like below. Note that most error checking/compiled delegate creation is omitted for the sake of brevity/simplicity. A cleaner, performance optimized version with a reasonable level of error checking can be found in this gist.
public class DefaultBuilderFor<T> : IImmutableBuilderFor<T> where T : class, IImmutableOf<T>
{
private static readonly IDictionary<string, Tuple<Type, Action<T, object>>> _setters;
private List<Action<T>> _mutations = new List<Action<T>>();
static DefaultBuilderFor()
{
_setters = GetFieldSetters();
}
public DefaultBuilderFor(T instance)
{
Source = instance;
}
public T Source { get; private set; }
public IImmutableBuilderFor<T> Set<TFieldType>(string fieldName, TFieldType value)
{
// Notes: error checking omitted & add what to do if `TFieldType` is not "correct".
_mutations.Add(inst => _setters[fieldName].Item2(inst, value));
return this;
}
public IImmutableBuilderFor<T> Set<TFieldType>(string fieldName, Func<T, TFieldType> valueProvider)
{
// Notes: error checking omitted & add what to do if `TFieldType` is not "correct".
_mutations.Add(inst => _setters[fieldName].Item2(inst, valueProvider(inst)));
return this;
}
public IImmutableBuilderFor<T> Set<TFieldType>(Expression<Func<T, TFieldType>> fieldExpression, TFieldType value)
{
// Error checking omitted.
var memberExpression = fieldExpression.Body as MemberExpression;
return Set<TFieldType>(memberExpression.Member.Name, value);
}
public IImmutableBuilderFor<T> Set<TFieldType>(Expression<Func<T, TFieldType>> fieldExpression, Func<TFieldType, TFieldType> valueProvider)
{
// Error checking omitted.
var memberExpression = fieldExpression.Body as MemberExpression;
var getter = fieldExpression.Compile();
return Set<TFieldType>(memberExpression.Member.Name, inst => valueProvider(getter(inst)));
}
public T Build()
{
var result = (T)Source.Clone();
_mutations.ForEach(x => x(result));
return result;
}
private static IDictionary<string, Tuple<Type, Action<T, object>>> GetFieldSetters()
{
// Note: can be optimized using delegate setter creation (IL).
return typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance)
.Where(x => !x.IsLiteral)
.ToDictionary(
x => x.Name,
x => SetterEntry(x.FieldType, (inst, val) => x.SetValue(inst, val)));
}
private static Tuple<Type, Action<T, object>> SetterEntry(Type type, Action<T, object> setter)
{
return Tuple.Create(type, setter);
}
}
Example usage
This could then be used like this, using your example class of State
:
public static class Example
{
public class State : IImmutableOf<State>
{
public State(int someInt, string someString)
{
SomeInt = someInt;
SomeString = someString;
}
public readonly int SomeInt;
public readonly string SomeString;
public IImmutableBuilderFor<State> Mutate()
{
return new DefaultBuilderFor<State>(this);
}
public object Clone()
{
return base.MemberwiseClone();
}
public override string ToString()
{
return string.Format("{0}, {1}", SomeInt, SomeString);
}
}
public static void Run()
{
var original = new State(10, "initial");
var mutatedInstance = original.Mutate()
.Set("SomeInt", 45)
.Set(x => x.SomeString, "Hello SO")
.Build();
Console.WriteLine(mutatedInstance);
mutatedInstance = original.Mutate()
.Set(x => x.SomeInt, val => val + 10)
.Build();
Console.WriteLine(mutatedInstance);
}
}
With the following output:
45, Hello SO
20, initial
这篇关于实例化不可变对象与反思的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!