调用内部或私有方法的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(或相似的函 数) ...
随机推荐
- sql计算众数及中位数
众数 众数: 情况①:一组数据中,出现次数最多的数就叫这组数据的众数. 举例:1,2,3,3,4的众数是3. 情况② :如果有两个或两个以上个数出现次数都是最多的,那么这几个数都是这组数据的众数. 举 ...
- bash shell 无法使用 perl 正则
哈喽大家好,我是咸鱼.今天跟大家分享一个关于正则表达式的案例,希望能够对你有所帮助 案例现象 前几天有一个小伙伴在群里求助,说他这个 shell 脚本有问题,让大家帮忙看看 可以看到,这个脚本首先 ...
- 从源码角度深入解析Callable接口
摘要:从源码角度深入解析Callable接口,希望大家踏下心来,打开你的IDE,跟着文章看源码,相信你一定收获不小. 本文分享自华为云社区<一个Callable接口能有多少知识点?>,作者 ...
- Django笔记二十七之数据库函数之文本函数
本文首发于公众号:Hunter后端 原文链接:Django笔记二十七之数据库函数之文本函数 这篇笔记将介绍如何使用数据库函数里的文本函数. 顾名思义,文本函数,就是针对文本字段进行操作的函数,如下是目 ...
- OpenCV-Python 中文教程
OpenCV-Python 中文教程 目录 I 走进 OpenCV 关于 OpenCV-Python 教程 在 Windows 上安装 OpenCV-Python 在 Fedora 上安装 OpenC ...
- 手记系列之四 ----- 关于使用MySql的经验
前言 本篇文章主要介绍的关于本人在使用MySql记录笔记的一些使用方法和经验,温馨提示,本文有点长,约1.5w字,几十张图片,建议收藏查看. 一.MySql安装 下载地址:https://dev.my ...
- MacOS 环境下 VSCode 的 C++ 环境搭建
编译器安装 编译器可以选择 Clang 或者 GCC,在 MacOS 上 Clang 的安装更为简单一些. Clang(推荐) 打开终端输入命令, clang -v 查看是否已经安装. 如果已经安装, ...
- 逍遥自在学C语言 | 条件控制的正确使用姿势
前言 在C语言中,有三种条件判断结构:if语句.if-else语句和switch语句. 一.人物简介 第一位闪亮登场,有请今后会一直教我们C语言的老师 -- 自在. 第二位上场的是和我们一起学习的小白 ...
- 2022-03-23:在k进制下,最小多小的num,可以让1~num范围的数拥有1的个数不少于n个? 腾讯音乐2022校园招聘。
2022-03-23:在k进制下,最小多小的num,可以让1~num范围的数拥有1的个数不少于n个? 腾讯音乐2022校园招聘. 答案2022-03-23: 二分法. 代码用golang编写.代码如下 ...
- Java配置线程池
一.Java配置线程池 1.线程池分类.其他 1.1.分类 IO密集型 和 CPU密集型 任务的特点不同,因此针对不同类型的任务,选择不同类型的线程池可以获得更好的性能表现. 1.1. IO密集型任务 ...