SoFunction
Updated on 2025-03-08

Detailed explanation of the code to optimize reflection calls using ReflectionDynamicObject on the .NET platform

Based on the principle of encapsulation, the designer of the API will hide some members (attributes, fields, methods, etc.) to ensure robustness. But there are always cases where direct access to these private members is required.

In order to access a type of private member, in addition to changing the API design, it is also necessary to use reflection technology:

public class MyApi
{
	public MyApi()
	{
		_createdAt = ;
	}
	private DateTime _createdAt;
	public int ShowTimes { get; private set; }
	public void ShowCreateTime()
	{
		(_createdAt);
		ShowTimes++;
	}
}

void Main()
{
	var api = new MyApi();
	var field = ().GetField("_createdAt",  | );
	var value = (api);
	(value);
}

This is not elegant:

The code is verbose and troublesome to write. The implementation is more confusing and not very intuitive.

Based on "dynamic type technology", the author explored a relatively elegant solution for beautifying the above code and named it ReflectionDynamicObject:

void Main()
{
    var api = new MyApi();
    dynamic wrapper = (api);
    (wrapper._createdAt);
}

In addition to supporting obtaining values, ReflectionDynamicObject also supports assignment:

void Main()
{
    var api = new MyApi();
    dynamic wrapper = (api);
    wrapper._createdAt = new DateTime(2022, 2, 2, 22, 22, 22);
    ();
}

In addition to fields, of course, operations on attributes are also supported:

void Main()
{
    var api = new MyApi();
    dynamic wrapper = (api);
     = 100;
    ();
}

In terms of support for properties, ReflectionDynamicObject uses "fast reflection" technology to generate delegates for value acquisition and copy operations to optimize performance.

The implementation principle of ReflectionDynamicObject

ReflectionDynamicObject is derived from DynamicObject, which obtains all properties and fields through reflection technology and stores its getter and setter methods and is called at runtime through the TryGetMember and TrySetMember methods.

Source code of ReflectionDynamicObject

public sealed class ReflectionDynamicObject : DynamicObject
{
    private readonly object _instance;
    private readonly Accessor _accessor;
    private ReflectionDynamicObject(object instance)
    {
        _instance = instance ?? throw new ArgumentNullException(nameof(instance));
        _accessor = GetAccessor(());
    }
    public static ReflectionDynamicObject Wrap(Object value)
        if (value == null) throw new ArgumentNullException(nameof(value));
        return new ReflectionDynamicObject(value);

    public override bool TryGetMember(GetMemberBinder binder, out object result)
        if (_accessor.TryFindGetter(, out var getter))
        {
            result = (_instance);
            return true;
        }
        return (binder, out result);
    public override bool TrySetMember(SetMemberBinder binder, object value)
        if (_accessor.TryFindSetter(, out var setter))
            (_instance, value);
        return (binder, value);
    #region Quick Reflection    private interface IGetter
        object Get(object instance);
    private interface ISetter
        void Set(object instance, object value);
    private class Getter : IGetter
        private FieldInfo _field;
        public Getter(FieldInfo field)
            _field = field ?? throw new ArgumentNullException(nameof(field));
        public object Get(object instance)
            return _field.GetValue(instance);
    private class Setter : ISetter
        public Setter(FieldInfo field)
        public void Set(object instance, object value)
            _field.SetValue(instance, value);
    private class Getter<T1, T2> : IGetter
        private readonly Func<T1, T2> _getter;
        public Getter(Func<T1, T2> getter)
            _getter = getter ?? throw new ArgumentNullException(nameof(getter));
            return _getter((T1)instance);
    private class Setter<T1, T2> : ISetter
        private readonly Action<T1, T2> _setter;
        public Setter(Action<T1, T2> setter)
            this._setter = setter ?? throw new ArgumentNullException(nameof(setter));
            this._setter.Invoke((T1)instance, (T2)value);
    private class Accessor
        public Accessor(Type type)
            this._type = type ?? throw new ArgumentNullException(nameof(_type));
            var getter = new SortedDictionary<string, IGetter>();
            var setter = new SortedDictionary<string, ISetter>();
            var fields = _type.GetFields( |  | );
            foreach (var field in fields)
            {
                getter[] = new Getter(field);
                setter[] = new Setter(field);
            }
            var props = _type.GetProperties( |  | );
            foreach (var item in props)
                if ()
                {
                    var method = ;
                    var funcType = typeof(Func<,>).MakeGenericType(, );
                    var func = (funcType);
                    var getterType = typeof(Getter<,>).MakeGenericType(, );
                    var get = (IGetter)(getterType, func);
                    getter[] = get;
                }
                if ()
                    var method = ;
                    var actType = typeof(Action<,>).MakeGenericType(, );
                    var act = (actType);
                    var setterType = typeof(Setter<,>).MakeGenericType(, );
                    var set = (ISetter)(setterType, act);
                    setter[] = set;
            _getters = getter;
            _setters = setter;
        private readonly Type _type;
        private readonly IReadOnlyDictionary<string, IGetter> _getters;
        private readonly IReadOnlyDictionary<string, ISetter> _setters;
        public bool TryFindGetter(string name, out IGetter getter) => _getters.TryGetValue(name, out getter);
        public bool TryFindSetter(string name, out ISetter setter) => _setters.TryGetValue(name, out setter);
    private static Dictionary<Type, Accessor> _accessors = new Dictionary<Type, Accessor>();
    private static object _accessorsLock = new object();
    private static Accessor GetAccessor(Type type)
        if (_accessors.TryGetValue(type, out var accessor)) return accessor;
        lock (_accessorsLock)
            if (_accessors.TryGetValue(type, out accessor)) return accessor;
            accessor = new Accessor(type);
            var temp = new Dictionary<Type, Accessor>(_accessors);
            temp[type] = new Accessor(type);
            _accessors = temp;
            return accessor;
    #endregion
}

Limitations of ReflectionDynamicObject

Based on complexity considerations, ReflectionDynamicObject does not add support for "methods". This means that the call to the method is missing. Although dynamic behavior allows the program to get rid of its dependence on strings, the implementation's support for "refactoring" is still unfriendly.

Where to use ReflectionDynamicObject?

Liquid Theme Engine is a set of HTML theme engines implemented by the author based on the Liquid language and Shopify theme mechanism and using the Fluid template engine. The engine allows end users to freely modify their theme templates without affecting the host. The ultimate goal is to achieve multilingual, multi-theme, high scalability, and what you see is what you get.

When writing the Liquid theme engine, I need to rewrite the render tag of the Fluid template engine to allow the subview to load from the snippets folder. When implementing this tag, you need to access the LocalScope and RootScope fields of the TemplateContext. Unfortunately, the above fields are marked internal and cannot be accessed in an external assembly. So there is a ReflectionDynamicObject to help me complete access to LocalScope and RootScope.

Reference link

Liquid template language:/liquid

Fluid template engine:/sebastienros/fluid

Liquid Theme Engine:/zyingnet_kf/liquid-theme-engine

This is the article about optimizing reflection call code using ReflectionDynamicObject on the .NET platform. For more related .NET ReflectionDynamicObject, please search for my previous article or continue browsing the related articles below. I hope everyone will support me in the future!