基于封装的原则,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);
}

这种写法并不优雅:

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

笔者基于“动态类型技术”探索出了一种相对来说比较优雅的方案用于美化上述代码,并为其命名为 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 true;
}
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
{
private FieldInfo _field;
public Setter(FieldInfo field)
{
_field = field ?? throw new ArgumentNullException(nameof(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));
}
public object Get(object instance)
{
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));
}
public void Set(object instance, object value)
{
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 优化反射调用代码的更多相关文章

  1. 深入分析Java反射(八)-优化反射调用性能

    Java反射的API在JavaSE1.7的时候已经基本完善,但是本文编写的时候使用的是Oracle JDK11,因为JDK11对于sun包下的源码也上传了,可以直接通过IDE查看对应的源码和进行Deb ...

  2. 使用dynamic类型来优化反射

    什么是dynamic类型?微软给出的官方文档中这样解释:在通过 dynamic 类型实现的操作中,该类型的作用是绕过编译时类型检查. 改为在运行时解析这些操作. dynamic 类型简化了对 COM ...

  3. 在C++中反射调用.NET(一)

    为什么要在C++中调用.NET 一般情况下,我们常常会在.NET程序中调用C/C++的程序,使用P/Invoke方式进行调用,在编写代码代码的时候,首先要导入DLL文件,然后在根据C/C++的头文件编 ...

  4. 反射-优化及程序集等(用委托的方式调用需要反射调用的方法(或者属性、字段),而不去使用Invoke方法)

    反射-优化及程序集等(用委托的方式调用需要反射调用的方法(或者属性.字段),而不去使用Invoke方法)   创建Delegate (1).Delegate.CreateDelegate(Type, ...

  5. Java 反射调用的一种优化

    写一些Java框架的时候,经常需要通过反射get或者set某个bean的field,比较普通的做法是获取field后调用java.lang.reflect.Field.get(Object),但每次都 ...

  6. 编写高质量代码改善C#程序的157个建议[为类型输出格式化字符串、实现浅拷贝和深拷贝、用dynamic来优化反射]

    前言 本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html .本文主要学习记录以下内容: 建议13.为类型输出格式化字符串 建议14.正确实现浅拷贝和深 ...

  7. 反射调用与Lambda表达式调用

    想调用一个方法很容易,直接代码调用就行,这人人都会.其次呢,还可以使用反射.不过通过反射调用的性能会远远低于直接调用——至少从绝对时间上来看的确是这样.虽然这是个众所周知的现象,我们还是来写个程序来验 ...

  8. 在C++中反射调用.NET(三)

    在.NET与C++之间传输集合数据 上一篇<在C++中反射调用.NET(二)>中,我们尝试了反射调用一个返回DTO对象的.NET方法,今天来看看如何在.NET与C++之间传输集合数据. 使 ...

  9. 反射(4)反射性能问题:直接调用vs反射调用

    很多人都说使用反射会有性能问题,那到底会比直接调用慢多少呢,下面就来测试一下. 直接调用vs反射调用 下面就来写个demo来验证下直接调用和反射调用的性能差异,代码如下: namespace Cons ...

随机推荐

  1. CTF-sql-order by盲注

    本文章只讨论了order by盲注,关于order by的报错等注入不在本文章讨论范围,另有文章. 让我们先来看看本文章所使用的表的内容,如下图: 接下来先了解一下order by的基础知识: ord ...

  2. AGC041F Histogram Rooks

    有生之年自己做出了一个 AGC F 还踩了标算,但是好像在我之前已经有人踩过标算了,再鞭尸一波也无可厚非 hhh 看到"全部被覆盖" 条件不好做,考虑容斥,即选择若干个位置强制它们 ...

  3. windows10双系统删除linux

    问题 在这里删除后会发现有残留一个引导区,几百m(下图已经删除完),而且启动会进linux引导,然后必须f12进入选择启动项才可以启动windows 解决方法 使用删除引导就可以了 再使用傲梅分区助手 ...

  4. 🏆【Alibaba中间件技术系列】「Nacos技术专题」配置中心加载原理和配置实时更新原理分析(上)

    官方资源 https://nacos.io/zh-cn/docs/quick-start.html Nacos之配置中心 动态配置管理是 Nacos的三大功能之一,通过动态配置服务,可以在所有环境中以 ...

  5. Tomcat-如何创建动态的web工程及目录介绍

    1,IDEA中如何创建动态web工程 (1)创建一个新模块 (2)选择你要创建什么类型的模块 (3)输入模块名,finish完成创建 创建成功如下图: 一般在WEB-INF下建一个lib目录 存放ja ...

  6. undo和redo的区别

    undo和redo的区别: undo一般用于事务的取消与回滚,记录的是数据修改前的值: redo一般用于恢复已确认但未写入数据库的数据,记录的是数据修改后的值.

  7. python 裴伯拉切数列

    裴伯拉切数列:从第三个元素开始,每个元素为该元素前面的两个元素的和. 裴伯拉切数列:0,1,1,2,3,5,8,13,21,34,55...... 求出小于n的裴伯拉切数列. def fibo(n): ...

  8. tcp|ip nagle算法

    在TCP传输数据流中,存在两种类型的TCP报文段,一种包含成块数据(通常是满长度的,携带一个报文段最多容纳的字节数),另一种则包含交互数据(通常只有携带几个字节数据). 对于成块数据的报文段,TCP采 ...

  9. JavaScripts之迪卡算法求积(n*n)适用于SKU信息计算等场景

    迪卡算法求积(n * n) 使用 array.reduce 的方式实现 笛卡尔积算法 const arr = [ ['黑色', '白色', '蓝色'], ['1.2KG', '2.0KG', '3.0 ...

  10. Node.js下载安装与配置(windows)

    一.Node.js下载 官网下载地址:下载 | Node.js 中文网 (nodejs.cn) 以Windows64位安装版为例,点击"Windows 安装包(.msi)"右侧的& ...