文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

在 .NET 平台使用 ReflectionDynamicObject 优化反射调用的代码详解

2024-04-02 19:55

关注

基于封装的原则,API 的设计者会将部分成员(属性、字段、方法等)隐藏以保证健壮性。但总有需要直接访问这些私有成员的情况。

为了访问一个类型的私有成员,除了更改 API 设计还有就是使用反射技术:

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

void Main()
{
	var api = new MyApi();
	var field = api.GetType().GetField("_createdAt", BindingFlags.NonPublic | BindingFlags.Instance);
	var value = field.GetValue(api);
	Console.WriteLine(value);
}

这种写法并不优雅:

代码冗长,编写麻烦。实现比较绕,不太直观。

笔者基于“动态类型技术”探索出了一种相对来说比较优雅的方案用于美化上述代码,并为其命名为 ReflectionDynamicObject :

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

除了支持获取值,ReflectionDynamicObject 还支持赋值:

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

除了字段,当然也支持对属性的操作:

void Main()
{
    var api = new MyApi();
    dynamic wrapper = ReflectionDynamicObject.Wrap(api);
    wrapper.ShowTimes = 100;
    Console.WriteLine(wraper.ShowTimes);
}

在对属性的支持上,ReflectionDynamicObject 使用了“快速反射”技术,将取值和复制操作生成了委托以优化性能。

ReflectionDynamicObject 的实现原理

ReflectionDynamicObject 派生自 DynamicObject ,其内部通过反射技术获取到所有的属性和字段并对其 getter 和 setter 方法进行存储并通过 TryGetMember 和 TrySetMember 方法经运行时调用。

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(instance.GetType());
    }
    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(binder.Name, out var getter))
        {
            result = getter.Get(_instance);
            return true;
        }
        return base.TryGetMember(binder, out result);
    public override bool TrySetMember(SetMemberBinder binder, object value)
        if (_accessor.TryFindSetter(binder.Name, out var setter))
            setter.Set(_instance, value);
        return base.TrySetMember(binder, value);
    #region 快速反射
    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(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            foreach (var field in fields)
            {
                getter[field.Name] = new Getter(field);
                setter[field.Name] = new Setter(field);
            }
            var props = _type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            foreach (var item in props)
                if (item.CanRead)
                {
                    var method = item.GetMethod;
                    var funcType = typeof(Func<,>).MakeGenericType(item.DeclaringType, item.PropertyType);
                    var func = method.CreateDelegate(funcType);
                    var getterType = typeof(Getter<,>).MakeGenericType(item.DeclaringType, item.PropertyType);
                    var get = (IGetter)Activator.CreateInstance(getterType, func);
                    getter[item.Name] = get;
                }
                if (item.CanWrite)
                    var method = item.SetMethod;
                    var actType = typeof(Action<,>).MakeGenericType(item.DeclaringType, item.PropertyType);
                    var act = method.CreateDelegate(actType);
                    var setterType = typeof(Setter<,>).MakeGenericType(item.DeclaringType, item.PropertyType);
                    var set = (ISetter)Activator.CreateInstance(setterType, act);
                    setter[item.Name] = 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
}

ReflectionDynamicObject 的局限性

基于复杂度的考虑,ReflectionDynamicObject 并未添加对“方法”的支持。这也就意味着对方法的调用是缺失的。虽然动态行为让程序摆脱了对字符串的依赖,但是该实现对“重构”的支持仍然不友好。

哪里用到了 ReflectionDynamicObject ?

Liquid 主题引擎 是笔者根据 Liquid 语言和 Shopify 主题机制并采用 Fluid 模板引擎实现的一套 HTML 主题引擎。该引擎允许最终用户自由的修改自己的主题模板而不会对宿主造成影响。最终目标是做到多语言、多主题、高扩展性以及所见即所得。

在编写 Liquid 主题引擎 时,笔者需要重写 Fluid 模板引擎的 render 标签让子视图从 snippets 文件夹加载。在实现该标签时,需要访问 TemplateContext 的 LocalScope 和 RootScope 字段,不幸的是上述字段被标记为了 internal ,无法在外部程序集中访问到。于是便有了 ReflectionDynamicObject ,帮助笔者完成对 LocalScope 和 RootScope 的访问。

参考链接

Liquid 模板语言: https://www.coderbusy.com/liquid

Fluid 模板引擎:https://github.com/sebastienros/fluid

Liquid 主题引擎:https://gitee.com/zyingnet_kf/liquid-theme-engine

到此这篇关于在 .NET 平台使用 ReflectionDynamicObject 优化反射调用代码的文章就介绍到这了,更多相关.NET  ReflectionDynamicObject 优化反射调用内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯