用reflection实例化不可变对象

我创建了一个基类来帮助我减少C#中不可变对象初始化的样板代码,

我正在使用延迟初始化以尽量不影响性能,我想知道通过这样做我有多大影响性能?

这是我的基类:

public class ImmutableObject { private readonly Func<IEnumerable<KeyValuePair>> initContainer; protected ImmutableObject() {} protected ImmutableObject(IEnumerable<KeyValuePair> 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>> init) { initContainer = init; } protected T setProperty(string propertyName, object propertyValue, bool lazy = true) { Func<IEnumerable<KeyValuePair>> mergeFunc = delegate { var propertyDict = initContainer == null ? ObjectToDictonary () : initContainer(); return propertyDict.Select(p => p.Key == propertyName? new KeyValuePair(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(mergeFunc())); } private IEnumerable<KeyValuePair> ObjectToDictonary() { var fields = GetType().GetFields().Where(f=> f.IsPublic); return fields.Select(f=> new KeyValuePair(f.Name, f.GetValue(this))).ToList(); } private static object DictonaryToObject(IEnumerable<KeyValuePair> 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(properties); } } 

可以像这样实现:

 public class State:ImmutableObject { public State(){} public State(IEnumerable<KeyValuePair> properties):base(properties) {} public State(Func<IEnumerable<KeyValuePair>> 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); } } 

并可以像这样使用:

 //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(); 

那么回答你关于性能的问题,反思非常昂贵(相对来说)。 如果它在性能关键代码中,我就不会使用你的设计。

在generics和reflection方面,性能影响通常会非常大。 考虑一下这么简单的事情:

 public class Builder where T : new() { public T Build() { return new T(); } } 

这实际上做的是调用Activator.CreateInstance ,它使用reflection,而且非常昂贵。

如果我想像上面的情况那样优化代码,我会使用动态方法 。 而两者之间的性能差异将是巨大的。

当然,请记住,为了提高性能,我们正在进入更复杂,更难阅读的高级代码区域。 您可以认为这种过度优化和过度杀害不是性能关键的代码。

但是在我写的代码中,我避免像瘟疫那样反思。

正如在评论中已经提到的那样,更有意义的是,不要将不可变实例实现或接口与实际上是新实例的构建器的行为“混淆”。

你可以用这种方式制作一个更清洁,更安全的解决方案。 所以我们可以定义一些标记接口并键入其安全版本:

 public interface IImmutable : ICloneable { } public interface IImmutableBuilder { } public interface IImmutableOf : IImmutable where T : class, IImmutable { IImmutableBuilderFor Mutate(); } public interface IImmutableBuilderFor : IImmutableBuilder where T : class, IImmutable { T Source { get; } IImmutableBuilderFor Set(string fieldName, TFieldType value); IImmutableBuilderFor Set(string fieldName, Func valueProvider); IImmutableBuilderFor Set(Expression> fieldExpression, TFieldType value); IImmutableBuilderFor Set(Expression> fieldExpression, Func valueProvider); T Build(); } 

并在如下所示的类中提供所有必需的基本构建器行为。 请注意,为简洁/简洁起见,省略了大多数错误检查/编译的委托创建。 可以在此要点中找到具有合理级别的错误检查的更清洁,性能优化的版本。

 public class DefaultBuilderFor : IImmutableBuilderFor where T : class, IImmutableOf { private static readonly IDictionary>> _setters; private List> _mutations = new List>(); static DefaultBuilderFor() { _setters = GetFieldSetters(); } public DefaultBuilderFor(T instance) { Source = instance; } public T Source { get; private set; } public IImmutableBuilderFor Set(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 Set(string fieldName, Func 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 Set(Expression> fieldExpression, TFieldType value) { // Error checking omitted. var memberExpression = fieldExpression.Body as MemberExpression; return Set(memberExpression.Member.Name, value); } public IImmutableBuilderFor Set(Expression> fieldExpression, Func valueProvider) { // Error checking omitted. var memberExpression = fieldExpression.Body as MemberExpression; var getter = fieldExpression.Compile(); return Set(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>> 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> SetterEntry(Type type, Action setter) { return Tuple.Create(type, setter); } } 

用法示例

然后可以像这样使用你的示例类State

 public static class Example { public class State : IImmutableOf { public State(int someInt, string someString) { SomeInt = someInt; SomeString = someString; } public readonly int SomeInt; public readonly string SomeString; public IImmutableBuilderFor Mutate() { return new DefaultBuilderFor(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); } } 

使用以下输出:

 45, Hello SO 20, initial 

我喜欢的方式是使用表达式树。 您可以手动构建表达式树,只需创建类型的新实例,并将此表达式树编译为委托。 这种方法的优点在于,您只需要reflection和动态代码生成一次,然后您就可以使用生成的委托。 此外,表达式编译器甚至可以在动态方法存在问题的部分可信环境中工作。 另一方面,你有一个抽象层比在ILGenerator中编写纯IL代码要高得多,这将是动态方法的一种方式。