基于封装的原则,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. python环境搭建以及jupyter notebook的安装和启动

    一.Python 环境搭建 本章节我们将向大家介绍如何在本地搭建Python开发环境. Python可应用于多平台包括 Linux 和 Mac OS X. 你可以通过终端窗口输入 "pyth ...

  2. JS调用堆栈

    调用栈 JavaScript 是一门单线程的语言,这意味着它只有一个调用栈,因此,它同一时间只能做一件事.如果我们运行到一个函数,它就会将其放置到栈顶.当从这个函数返回的时候,就会将这个函数从栈顶弹出 ...

  3. Servlet Cookie的使用

    HTTP(超文本传输协议)是一个基于请求与响应模式的无状态协议.无状态主要指 2 点: 协议对于事务处理没有记忆能力,服务器不能自动维护用户的上下文信息,无法保存用户状态: 每次请求都是独立的,不会受 ...

  4. DB2重组表失败处理办法

    set integrity for XXX all immediate unchecked reorg table XXX allow no access 如set integrity for a a ...

  5. 推荐召回--基于内容的召回:Content Based

    目录 1. 前言 2. 构建画像 3. 内容召回的算法 1. 前言 在之前总结过协同过滤的召回通路后,今天我们来总结下召回策略中的重头戏:基于内容的召回通路,也即我们常说的基于标签的召回.这里就要涉及 ...

  6. 来自开发者的点赞!HMS Core荣获多个行业奖项

    2021年,HMS Core发布全新HMS Core 6,为全球开发者提供多终端.跨OS.全场景的华为移动服务核心能力,和开发者共同成长.通过和开发者在行业解决方案.业务场景创新和商业增长上的持续合作 ...

  7. Java语法专题2: 类变量的初始化顺序

    合集目录 Java语法专题2: 类变量的初始化顺序 问题 这也是Java面试中出镜率很高的基础概念问题 描述一下多级继承中字段初始化顺序 描述一下多级继承中类变量初始化顺序 写出运行以下代码时的控制台 ...

  8. spring 整合shiro框架 模拟登录控制器。

    一.导入shiro  jar包.  我在maven项目中,将常用的jar包都放在里面. <?xml version="1.0" encoding="UTF-8&qu ...

  9. C++智能指针使用说明

    导读 STL提供四种智能指针:auto_ptr.unique_ptr.shared_ptr和weak_ptr.其中auto_ptr是C++98提供的解决方案,C++11以后均已摒弃.所有代码在gcc ...

  10. 计算机网络再次整理————tcp例子第二前奏[四]

    前言 前文我们介绍了网络协议的各层,同时也介绍了一下我们在编写代码时候的服务端的accept.bind.listen.connect.send做了什么. 可以说是从宏观的角度,或者代码开发的角度来说的 ...