基于封装的原则,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. 解决twrp中内部存储为0MB的情况

    本来打算给备用机红米4a刷个dotos的系统,结果忘记双清就刷了,然后进去系统也是直接黑屏,很神奇的是长按电源键能弹出dotos的关机选项.然后进去twrp准备双清在刷时,发现内部存储变成了0MB,然 ...

  2. 【刷题-LeetCode】205. Isomorphic Strings

    Isomorphic Strings Given two strings *s* and *t*, determine if they are isomorphic. Two strings are ...

  3. 2021年SpringBoot面试题200道及答案

    https://blog.csdn.net/yanpenglei/article/details/120822218 https://blog.csdn.net/ldb987/article/deta ...

  4. Docker 与 K8S学习笔记(十九)—— Pod的配置管理

    我们在部署应用时常常会考虑将应用程序与配置文件相分离,这样可以使应用程序更好的复用,并且通过不同配置也能实现更灵活的功能.将应用制作成镜像后,我们可以在启动容器时通过环境变量或挂载文件的方式注入,但是 ...

  5. 挂载的卸载与运行 关闭selinux

    目录 一:系统目录结构介绍 1 定义挂载设备信息 光驱设备 /cd/cdrom 2 完成设备挂载操作 运行挂载 mount/dev/cdrom /mnt/ 3 检查测试挂载结果 挂载检查 df -h ...

  6. 使用Hot Chocolate和.NET 6构建GraphQL应用(3) —— 实现Query基础功能

    系列导航 使用Hot Chocolate和.NET 6构建GraphQL应用文章索引 需求 在本文中,我们通过一个简单的例子来看一下如何实现一个最简单的GraphQL的接口. 实现 引入Hot Cho ...

  7. Unity——日志打印工具

    一.日志工具功能 封装Debug类,需要实现功能: 1.控制所有日志是否打印: 2.除了Log,Warning,Error外,给更多日志种类(不同颜色): 3.格式化打印日志: 4.不定参数,自动拼接 ...

  8. 中文分词,自然语言处理器NLP。 六月份版本已上线。

    一,  没有对比,就没有伤害,我们分词的优势在哪里?走一波测试. 跑一下CaCl2,看看效果. 二   想要分什么词汇,自己自定义即可. 目前每个月都会出一个新的版本,主要是和金融相关的词汇. 这是6 ...

  9. 利用 Python 进行数据分析(Python 数据分析)· 第 2 版

    译者:SeanCheney 欢迎任何人参与和完善:一个人可以走的很快,但是一群人却可以走的更远. ApacheCN 机器学习交流群 629470233 ApacheCN 学习资源 Sklearn 与 ...

  10. Java--这张线程瓜图保熟,不进来看看?这瓜要是不熟它就Dead掉了

    先来看一下线程这张图线程的几种运行状态之间运行流程: 看不懂没关系,慢慢来学习,往下学习来继续了解一下~ 什么是线程? 线程是进程的一部分,是程序执行中的一条执行路线: 进程就是指程序在其自身地址空间 ...