用反射实例化不可变对象

时间:2014-06-24 19:59:32

标签: 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);
    }
}

可以这样实现:

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);
    }
}

可以像这样使用:

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

3 个答案:

答案 0 :(得分:6)

正如评论中已经提到的那样,它会更有意义,而不是&#34; conflate&#34;不可变实例实现或接口,其中包含新实例的构建器的行为。

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

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

并在下面的类中提供所有必需的基本构建器行为。注意,为了简洁/简洁起见,省略了大多数错误检查/编译的委托创建。可以在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);
    }
}

使用示例

然后可以使用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);
    }
}

使用以下输出:

45, Hello SO
20, initial

答案 1 :(得分:4)

很好地回答你关于表现的问题,反思非常昂贵(相对而言)。如果它在性能关键代码中,我就不会使用你的设计。

当涉及到泛型和反射时,性能影响通常会非常大。考虑一下这么简单的事情:

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

这实际上做的是调用使用反射的Activator.CreateInstance并且它非常昂贵。

如果我想像上述情况那样优化代码,我会使用dynamic methods。两者之间的性能差异很大。

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

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

答案 2 :(得分:2)

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