调用内部或私有方法的N种方法
非公开的类型或者方法被“隐藏”在程序集内部,本就不希望从外部访问,但是有时候调用一个内部或者私有方法可能是唯一的“救命稻草”,这篇文章列出了几种具体的实现方式。以如下这个Foobar类型为例,它具有一个内部属性InternalValue,我们来看看有多少种方式可以从外部获取一个Foobar对象的InternalValue属性值。
public class Foobar
{
internal int InternalValue => 123;
}
一、反射
对于大部分人来说,最先想到的自然是“反射”,具体实现体现再如下所示的InternalValueAccessor类型的GetInternalValue方法中。但是我们都知道反射是一种并不高效的方式,对于需要频繁调用,我们一般不推荐使用。
var foobar = new Foobar();
Debug.Assert(InternalValueAccessor.GetInternalValue(foobar) == 123); public static class InternalValueAccessor
{
public static int GetInternalValue(Foobar foobar)
{
var propertyInfo = typeof(Foobar).GetProperty("InternalValue", BindingFlags.Instance | BindingFlags.NonPublic)!;
return (int)propertyInfo.GetValue(foobar)!;
}
}
二、MethodInfo.CreateDelegate方法
要获得Foobar对象的InternalValue属性值(int类型),实际上需要一个Func<Foobar,int>类型的委托。由于返回值实际上是通过InternalValue属性的Get方法获得的,而表示方法的MethodInfo类型具有一个CreateDelegate<TDelegate>方法,我们可以采用如下的方式利用InternalValue属性的Get方法来创建所需的Func<Foobar,int>委托。
var foobar = new Foobar();
Debug.Assert(InternalValueAccessor.GetInternalValue(foobar) == 123); public static class InternalValueAccessor
{
private static Func<Foobar, int>? _getInternalValue;
public static int GetInternalValue(Foobar foobar)=> (_getInternalValue??= CreateDelegate())(foobar);
private static Func<Foobar, int> CreateDelegate()
{
var methodInfo = typeof(Foobar).GetProperty("InternalValue", BindingFlags.Instance | BindingFlags.NonPublic)!.GetMethod!;
return methodInfo.CreateDelegate<Func<Foobar, int>>();
}
}
三、表达式(树)
一般来说,所有的反射解决方案都可以转换成基于表达式(树)的解决方案。我们需要的Func<Foobar,int>委托可以按照如下的方式,利用构建的表达式编译生成。
public static class InternalValueAccessor
{
private static Func<Foobar, int>? _getInternalValue;
public static int GetInternalValue(Foobar foobar)=> (_getInternalValue??= CreateDelegate())(foobar);
private static Func<Foobar, int> CreateDelegate()
{
var methodInfo = typeof(Foobar).GetProperty("InternalValue", BindingFlags.Instance | BindingFlags.NonPublic)!.GetMethod!;
var foobar = Expression.Parameter(typeof(Foobar), "foobar");
var getValue = Expression.Call(foobar, methodInfo);
return Expression.Lambda<Func<Foobar, int>>(getValue, foobar).Compile();
}
}
四、动态方法(call)
实际上表达式(树)是对IL代码的抽象表达,所以既然这样的问题自然可以利用IL Emit来解决。在如下的代码中,我们创建了一个DynamicMethod类型表示的动态方法,以IL Emit的方式利用IL指令Call完成了针对InternalValue属性的Get方法的调用。我们所需的Func<Foobar,int>委托最终由这个DynamicMethod对象创建而成。
public static class InternalValueAccessor
{
private static Func<Foobar, int>? _getInternalValue;
public static int GetInternalValue(Foobar foobar) => (_getInternalValue ??= CreateDelegate())(foobar);
private static Func<Foobar, int> CreateDelegate()
{
var methodInfo = typeof(Foobar).GetProperty("InternalValue", BindingFlags.Instance | BindingFlags.NonPublic)!.GetMethod!;
var method = new DynamicMethod("GetInternalValue", typeof(int), new Type[] { typeof(Foobar) });
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.EmitCall(OpCodes.Call, methodInfo, null);
il.Emit(OpCodes.Ret);
return method.CreateDelegate<Func<Foobar, int>>();
}
}
五、动态方法(calli)
了解IL的朋友应该知道,方法调用涉及的IL治理有三个(Call、Callvir和Calli)。如果使用Calli指令,在完成针对参数的压栈之后,我们还需要执行Ldftn指令将方法指针压入栈中,最终执行Calli指令完成方法的执行。
public static class InternalValueAccessor
{
private static Func<Foobar, int>? _getInternalValue;
public static int GetInternalValue(Foobar foobar) => (_getInternalValue ??= CreateDelegate())(foobar);
private static Func<Foobar, int> CreateDelegate()
{
var methodInfo = typeof(Foobar).GetProperty("InternalValue", BindingFlags.Instance | BindingFlags.NonPublic)!.GetMethod!;
var method = new DynamicMethod("GetInternalValue", typeof(int), new Type[] { typeof(Foobar) });
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldftn, methodInfo);
il.EmitCalli(OpCodes.Calli, CallingConventions.Standard, typeof(int), new Type[] { typeof(Foobar) }, null);
il.Emit(OpCodes.Ret);
return method.CreateDelegate<Func<Foobar, int>>();
}
}
调用内部或私有方法的N种方法的更多相关文章
- Shell脚本中引用、调用另一个脚本文件的2种方法
Shell脚本中引用.调用另一个脚本文件的2种方法 http://www.jb51.net/article/67903.htm
- css控制div显示/隐藏方法及2种方法比较原码 - czf164的专栏 - 博客频道 - CSDN.NET
body{ font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI& ...
- python第三方包安装方法(两种方法)
具体有以下两种方法: 第一种方法(不使用pip或者easy_install): Step1:在网上找到的需要的包,下载下来.eg. rsa-3.1.4.tar.gz Step2:解压缩该文件. Ste ...
- wordpress调用置顶文章sticky_posts的三种方法
有时我们在开发wordpress时需要调用置顶文章sticky_posts,怎么调用呢?几种写法,有用到query_post的,有用到WP_Query,也有用到is_sticky(),下面随ytkah ...
- Ajax跨域的几种方法以及每种方法的原理
js中几种实用的跨域方法原理详解 这里说的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同域的框架中(iframe)的数据.只要协 ...
- javascript实现图片延迟加载方法汇总(三种方法)
看到一些大型网站,页面如果有很多图片的时候,当你滚动到相应的行时,当前行的图片才即时加载的,这样子的话页面在打开只加可视区域的图片,而其它隐藏的图片则不加载,一定程序上加快了页面加载的速度,跟着小编一 ...
- MFC 在对话框显示图片的多种方法(四种方法)
我们先从简单的开始吧.先分一个类: (一) 非动态显示图片(即图片先通过资源管理器载入,有一个固定ID) (二) 动态载入图片(即只需要在程序中指定图片的路径即可载入) 为方便说明,我们已经建好一个基 ...
- JavaScript中的方法事件和函数的方法的三种方法
js中的很多事件 而事件相对应的就是方法(函数 )那么今天所说的就是这三种方法 已onclick事件为例 1: 基本方法 <div id="a" onclick= ...
- Qt 设置背景图片3种方法(三种方法:QPalette调色板,paintEvent,QSS)
方法1. setStylSheet{"QDialog{background-image:url()"}} //使用styleSheet 这种方法的好处是继承它的dialog都会自 ...
- QT 调用 DLL 方法(三种方法)
Qt调用DLL方法一:使用Win32 API 在显式链接下,应用程序必须进行函数 调用以在运行时显式加载 DLL.为显式链接到 DLL,应用程序必须:? 调用 LoadLibrary(或相似的函 数) ...
随机推荐
- Kubuesphere部署Ruoyi(二):部署kubesphere
先决条件: 更换DNS 更换apt的镜像源 Ubuntu下永久性修改DNS vi /etc/systemd/resolved.conf DNS字段取消注释,并修改DNS为223.5.5.5 223.5 ...
- js-函数记忆
函数记忆: 指将上次的(计算结果)缓存起来,当下次调用时,如果遇到相同的(参数),就直接返回(缓存中的数据). 实现原理:将参数和对应的结果保存在对象中,再次调用时,判断对象 key 是否存在,存在返 ...
- Spring注解@Conditional相关用法
1.@Conditional注解 @Conditional 是Spring4新提供的注解. 它的作用是按照一定的条件进行判断,满足条件给容器注册bean,否则不注入. 可以作用在方法上,也可以作用在类 ...
- TensorFlow - 框架实现中的三种 Graph
文章目录 TensorFlow - 框架实现中的三种 Graph 1. Graph 2. GraphDef 3. MetaGraph 4. Checkpoint 5. 总结 TensorFlow - ...
- 任务系统之Jenkins子任务
今天下班即开启五一假期,早上临时定了行程去山东日照,原本计划下班就出发,但下班看了看导航,这一路红得发黑,于是决定还是晚点再走,现在有时间了,写篇简单的技术文章来提升下Blog逐渐降低的技术内容含量吧 ...
- 【Redis】Redis 编译安装配置优化,多实例配置
一.Redis 配置详解 # Redis configuration file example. # # Note that in order to read the configuration fi ...
- 万字长文详解如何使用Swift提高代码质量
前言 京喜APP最早在2019年引入了Swift,使用Swift完成了第一个订单模块的开发.之后一年多我们持续在团队/公司内部推广和普及Swift,目前Swift已经支撑了70%+以上的业务.通过使用 ...
- 2022-10-18:以下go语言代码输出什么?A:panic;B:编译错误;C:moonfdd1。 package main import ( “fmt“ “net/url“ ) // 其中
2022-10-18:以下go语言代码输出什么?A:panic:B:编译错误:C:moonfdd1. package main import ( "fmt" "net/u ...
- 2022-09-13:给你两个整数 m 和 n ,分别表示一块矩形木块的高和宽。 同时给你一个二维整数数组 prices ,其中 prices[i] = [hi, wi, pricei] 表示你可以
2022-09-13:给你两个整数 m 和 n ,分别表示一块矩形木块的高和宽. 同时给你一个二维整数数组 prices ,其中 prices[i] = [hi, wi, pricei] 表示你可以以 ...
- 2022-08-23:以下go语言代码输出什么?A:map[baz:2 foo:0];B:map[bar:1 baz:2];C:map[baz:2];D:不确定。 package main impo
2022-08-23:以下go语言代码输出什么?A:map[baz:2 foo:0]:B:map[bar:1 baz:2]:C:map[baz:2]:D:不确定. package main impor ...