基于封装的原则,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. 【刷题-LeetCode】164 Maximum Gap

    Maximum Gap Given an unsorted array, find the maximum difference between the successive elements in ...

  2. Cesium中级教程9 - Advanced Particle System Effects 高级粒子系统效应

    Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com/ 要了解粒子系统的基础知识,请参见粒子系统入门教程. Weathe ...

  3. 论文翻译:2021_DeepFilterNet: A Low Complexity Speech Enhancement Framework for Full-Band Audio based on Deep Filtering

    论文地址:DeepFilterNet:基于深度滤波的全频带音频低复杂度语音增强框架 论文代码:https://github.com/ Rikorose/DeepFilterNet 引用:Schröte ...

  4. 在Django中使用zerorpc

    在Django中使用zerorpc 前言 随着系统架构从集中式单点服务器到分布式微服务方向的迁移,RPC是一个不可回避的话题.如何在系统中引入对开发者友好,性能可靠的RPC服务是一个值得深思的问题. ...

  5. dataTaDataTable 详细教程

    DataTable 选项说明 特性 jQueryUI:true/false:控制是否使用jqueryUI样式,需要引入jQueryUI的CSS autoWidth:true/false:控制Datat ...

  6. POSIX之消息队列

    my_semqueue_send.c: #include<stdio.h> #include<errno.h> #include<mqueue.h> #includ ...

  7. Android开发----Button组件的使用与练习

    Button 学习目标: 文字大小.颜色 自定义背景形状 自定义按压效果 点击事件 创建一个新的Activity以增加控件 1.文字大小.颜色 直接在xml文件中定义即可 <Button and ...

  8. PyTorch 介绍 | DATSETS & DATALOADERS

    用于处理数据样本的代码可能会变得凌乱且难以维护:理想情况下,我们希望数据集代码和模型训练代码解耦(分离),以获得更好的可读性和模块性.PyTorch提供了两个data primitives:torch ...

  9. dp学习(二)

    树和图上的dp. 4. 简单树形dp 这些是最为简单的树形dp. 一般来说,树形dp是通过子树的dp值推出当前点的dp值. 在这里,我们默认当前节点为u,它的儿子节点为v,树的根为rt. 例题4.1 ...

  10. HTML横向二级导航

    图片素材没有发,就一个logo还有一个Nav背景图. 效果 HTML <!DOCTYPE html> <html lang="zh-cn"> <hea ...