用反射实例化不可变对象

Cou*_*ren 5 c# linq reflection initialization immutability

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

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

这是我的基类:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

可以像这样实现:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

并可以像这样使用:

//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();
Run Code Online (Sandbox Code Playgroud)

Ale*_*lex 6

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

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

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();
}
Run Code Online (Sandbox Code Playgroud)

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

用法示例

然后可以像这样使用你的示例类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);
    }
}
Run Code Online (Sandbox Code Playgroud)

使用以下输出:

45, Hello SO
20, initial
Run Code Online (Sandbox Code Playgroud)


Zer*_*er0 4

好吧,回答你关于性能的问题,反射是非常昂贵的(相对而言)。如果你的设计是性能关键的代码,我不会使用它。

当涉及到泛型和反射时,性能损失通常会大得惊人。甚至考虑像这样简单的事情:

public class Builder<T> where T : new()
{
    public T Build()
    {
        return new T();
    }
}
Run Code Online (Sandbox Code Playgroud)

这实际上是在调用Activator.CreateInstance使用反射的方法,而且它的成本非常高。

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

当然,请记住,我们正在进入高级代码区域,出于性能考虑,该区域更加复杂且难以阅读。您可以认为这在性能不关键的代码中过度优化和过度杀伤。

但在我编写的代码中,我像躲避瘟疫一样避免反射。