在.NET Core中使用DispatchProxy“实现”非公开的接口
原文地址:“Implementing” a non-public interface in .NET Core with DispatchProxy
原文作者:Filip W.
译文地址:https://www.cnblogs.com/lwqlun/p/11575686.html
译者:Lamond Lu
简介
反射是.NET中一个非常强大的概念,对于每一个C#开发人员来说,迟早都会使用到这个它。在许多场景中,反射都非常有用,例如程序集扫描,类型发现或者各种程序组合使用。
然而,它经常被用来绕过你正在使用的依赖项的public接口 - 修改它们或者访问依赖项做着未曾预想的内容。这就是说,这种“黑客入侵”的方式对于C#开发来说非常的典型,尽管有一定的风险,但是它可能有时候是让你摆脱编码困境的唯一方法。
如果你被迫公开一个非public(例如可能是internal的)接口的实现,那么事情就开始变得有趣了。针对这个问题,“基本的”反射已经不能带来任何帮助了,所以让我们来一起看一下我们应该如何实现这个需求。
示例问题
想想一下,你正在使用一个第三方库,在这个库中包含一下的内部类Greeter
。
internal class Greeter
{
public static void Greet(IGreeting greeting)
{
Console.WriteLine(greeting.Message);
}
}
现在呢,我们希望通过反射,使用这个类型,执行它其中定义的Greet
方法。为了实现这个需求,你需要一个实现IGreeting
接口的实现类实例,因为它是Greet
方法所需的参数。IGreeting
接口的代码如下:
internal interface IGreeting
{
string Message { get; }
}
这里需要注意的是,这里没有任何一个你可以直接使用的IGreeting
接口的实现。相反的,要使用Greeter
类,你就必须自己提供一个IGreeting
接口的实现。
当然,使用C#实现一个接口很简单 - 但是如何实现一个通过反射提取到的接口?好吧,这有一点问题,不是么?下面的代码,也对此进行了说明,注意该示例代码中的类与Greeter
和IGreeting
类型存在于不同的程序集中。
class Program
{
static void Main(string[] args)
{
// 查找非公开Greeter类型
var greeterType = Assembly.Load("Library").GetType("Library.Greeter");
// 提取Greet方法
var greetMethod = internalType.GetMethod("Greet", BindingFlags.Public | BindingFlags.Static);
// 尝试执行方法,然而...
// ...我们需要一个IGreeting接口类型的实例,我们该怎么办?
var greeting = greetMethod.Invoke(null, new[] { ??? });
Console.WriteLine();
}
}
DispatchProxy
下面让我们来使用DispatchProxy
类。这个类型自.NET Core诞生之日起,就已经存在了,它提供了实例化代理对象和处理器方法分发的机制。DispatchProxy
类的典型用法如下:
var proxy = DispatchProxy.Create<IFoo, FooProxy>();
这我们的示例中,IFoo
是我们需要实现的接口。DispatchProxy
的强大功能如下:它允许我们创建一个FooProxy
类型,该类型可以像IFoo
一样被使用,且不需要真正"实现它"。(或者它也可以转发给另一个实际上模拟IFoo
接口的类型)
但是,当使用以上API的时候,代理类实现的接口类型需要在编译时被知晓,这对于我们当前的用例不太理想 - 因为在非public的接口情况下,我们只能在运行时才能抓住它。不过不用担心,我们将使用反射解决这个问题。以下的代码说明了我们的做法(假设IFoo
是非public的):
var internalType = Assembly.Load("Library").GetType("IFoo");
var proxy = typeof(DispatchProxy)
.GetMethod(nameof(DispatchProxy.Create))
.MakeGenericMethod(internalType, typeof(FooProxy))
.Invoke(null, null);
最后就很简单了,我们使用与之前相同的Api, 但是我们可以动态的提供必要的参数类型,而不必在编译时才知道它们才能在泛型中使用。
在我们特定的Greeting
例子中,用于创建代理的方法如下:(为了更清晰的分离,我们将它封装在一个工厂类中)。
public class GreetingFactory
{
public static object Create()
{
var internalType = Assembly.Load("Library").GetType("Library.IGreeting");
return typeof(DispatchProxy)
.GetMethod(nameof(DispatchProxy.Create))
.MakeGenericMethod(internalType, typeof(GreetingProxy))
.Invoke(null, null);
}
}
现在,谜题的最后一块碎片就是实现GreetingProxy
了。如下的代码展示了GreetingProxy
类的实现,它是DispatchProxy
的一个子类。
public class GreetingProxy : DispatchProxy
{
private GreetingImpl _impl;
public GreetingProxy()
{
_impl = new GreetingImpl();
}
protected override object Invoke(MethodInfo targetMethod, object[] args)
{
return _impl.GetType().GetMethod(targetMethod.Name).Invoke(_impl, args);
}
private class GreetingImpl // : 不实现IGreeting, 但是模拟了它
{
public string Message => "hello world";
}
}
如你所见,这个类充当了潜在调用者与IGreeting
实际实现之间的网关,毕竟这就是代理的主要作用。这个"实现"(我用引号引起来,因为我们并不是真正实现非public接口),或者更确切的说,使用私有类GreetingImpl
的形式模仿接口类型,并包含了必要的public属性Message
。这里并不是必须要要这么做,这只是我自己喜欢的一种实现方式。
每当代用代理类的时候,我们都会可以根据请求的接口成员获得MethodInfo
信息 - 因此,我们只需将其重定向到结构相同的隐藏实现GreetingImpl
的相应成员即可。
最后,我们的代码看起来应该是这样的。
class Program
{
static void Main(string[] args)
{
var internalType = Assembly.Load("Library").GetType("Library.Greeter");
var greetMethod = internalType.GetMethod("Greet", BindingFlags.Public | BindingFlags.Static);
var proxy = GreetingFactory.Create();
Console.WriteLine(greetMethod.Invoke(null, new[] { proxy }));
}
}
那么,这个方法到底是用来做什么的呢?它通过代理对象,调用了GreetingImpl
中定义的方法,打印出了"Hello World"。当然,最终的结果是我们设法“实现”并使用了非公开API中的非public接口。
这在真实需求中有用么?
就像其他所有东西一样,我觉着答案 - 取决于 -毕竟它是一个高度专业化的API。这种技术(代理对象)经常会在ORM和其他Mock框架中使用。另外,如果你使用的是复杂的第三方框架或库,并且需要使用大量的反射,那么你迟早会用到DispatchProxy
。
实际上,如果你对真实需求的例子感兴趣, 你可以来看看我们的OmniSharp项目。OmniSharp项目使用Roslyn编译器为VSCode等许多代码编辑器提供代码感知功能。但是不幸的是,Roslyn并不会提供大量public API, 所以我们不得不大量使用反射。实际上,我们还必须在许多地方使用DispatchProxy
才能向用户提供一些特定功能,例如从类型中提取接口。一方面,这不是很友好,因为东西很容易崩溃,但是对于客户的价值是毋庸置疑的,所以我们还是选择这样做。
在.NET Core中使用DispatchProxy“实现”非公开的接口的更多相关文章
- 在Asp.Net Core中使用中间件保护非公开文件
在企业开发中,我们经常会遇到由用户上传文件的场景,比如某OA系统中,由用户填写某表单并上传身份证,由身份管理员审查,超级管理员可以查看. 就这样一个场景,用户上传的文件只能有三种人看得见(能够访问) ...
- 【春华秋实】深入源码理解.NET Core中Startup的注册及运行
写在前面 开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程序的起点.通过使用Startup,可以配置化处理所有向应用程序所做的请求的 ...
- 玩转ASP.NET Core中的日志组件
简介 日志组件,作为程序员使用频率最高的组件,给程序员开发调试程序提供了必要的信息.ASP.NET Core中内置了一个通用日志接口ILogger,并实现了多种内置的日志提供器,例如 Console ...
- ASP.NET Core中的依赖注入(3): 服务的注册与提供
在采用了依赖注入的应用中,我们总是直接利用DI容器直接获取所需的服务实例,换句话说,DI容器起到了一个服务提供者的角色,它能够根据我们提供的服务描述信息提供一个可用的服务对象.ASP.NET Core ...
- ASP.NET Core 中文文档 第三章 原理(6)全球化与本地化
原文:Globalization and localization 作者:Rick Anderson.Damien Bowden.Bart Calixto.Nadeem Afana 翻译:谢炀(Kil ...
- ASP.NET Core 中文文档 第三章 原理(11)在多个环境中工作
原文: Working with Multiple Environments 作者: Steve Smith 翻译: 刘浩杨 校对: 孟帅洋(书缘) ASP.NET Core 介绍了支持在多个环境中管 ...
- ASP.NET Core 中文文档 第三章 原理(12)托管
原文:Hosting 作者:Steve Smith 翻译:娄宇(Lyrics) 校对:何镇汐.许登洋(Seay) 为了运行 ASP.NET Core 应用程序,你需要使用 WebHostBuilder ...
- ASP.NET Core 中文文档 第三章 原理(13)管理应用程序状态
原文:Managing Application State 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:高嵩 在 ASP.NET Core 中,有多种途径可以对应用程序的状态进行 ...
- .NET Core中使用Razor模板引擎
一.简介 在MVC以外的场景中,我们往往需要完成一些模板引擎生成代码或页面的工作:在以前我们一般常用的有Razor.NVeocity.VTemplate.虽然所有的模板系统都具有一些共同特征,但 Ra ...
随机推荐
- 性能测试学习第六天-----JMeter拓展应用
一.TCP取样器 服务器名称或IP:填写socket接口的ip 端口号:写socket接口的端口号 Re-use connection:是否重用链接,如果选择,同一个线程执行的所有请求都会使用一个tc ...
- 01 Python网络爬虫简介
什么是爬虫 爬虫就是通过编写程序模拟浏览器上网,然后去互联网上爬取/获取数据的过程. 爬虫的分类 - 通用爬虫:就是爬取互联网中的一整张页面内容. - 聚焦爬虫:根据指定的需求爬取页面中指定的局部内容 ...
- python 编码报错问题 'ascii' codec can't encode characters 解决方法
python在安装时,默认的编码是ascii, 当程序中出现非ascii编码时,python的处理常常会报这样的错 'ascii' codec can't encode characters pyth ...
- .NET Core 小程序开发零基础系列(2)——小程序服务通知(模板消息)
基于上一篇文件“.NET Core 小程序开发零基础系列(1)——开发者启用并校验牵手成功”的反映,个人觉得效果很不错,大家对公众号开发还是有很大需求的,同时也收到了很多同学的问题,后面我也会通过实战 ...
- C++ 重载运算符(详)
C++ 重载运算符 C 重载运算符 一重载函数 1例程 2备注 二重载运算符 11 二元运算符重载 11 一元运算符重载 111 -- 2备注 3 特殊运算符重载 31 号运算符 32 下标运算符 3 ...
- HTML+JavaScript自己动手做日历
当我们需要在页面中显示某月的事项,或是选择某一段日期时,常常要使用到日历组件.这一组件同样有着许多现成的类库,然而亲自动手开发一个日历,从中了解其实现原理也是非常必要的.在本例中我们就将制作一款非常经 ...
- Android进阶之路(2)-详解MVP
### MVP简介 >MVP 全称:Model-View-Presenter :MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的[地方](https://baike.baidu.co ...
- 系统模块 OS
os.system("系统命令") 调用系统命令 os.system("task kill /f /im 系统的进程") 关闭系统进程 os.listdir( ...
- HDU-6333 Problem B. Harvest of Apples 莫队
HDU-6333 题意: 有n个不同的苹果,你最多可以拿m个,问有多少种取法,多组数据,组数和n,m都是1e5,所以打表也打不了. 思路: 这道题要用到组合数的性质,记S(n,m)为从n中最多取m个的 ...
- POJ 1797-Heavy Transportation-dijkstra小变形和POJ2253类似
传送门:http://poj.org/problem?id=1797 题意: 在起点和终点间找到一条路,使得经过的边的最小值是最大的: 和POJ2253类似,传送门:http://www.cnblog ...