Instantiating Immutable Objects With Reflection

前端 未结 3 1709
我寻月下人不归
我寻月下人不归 2021-02-10 08:03

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 imp

3条回答
  •  你的背包
    2021-02-10 08:14

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

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

    Example usage

    This could then be used like this, using your example class of 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);
        }
    }
    

    With the following output:

    45, Hello SO
    20, initial
    

提交回复
热议问题