原文地址:“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#实现一个接口很简单 - 但是如何实现一个通过反射提取到的接口?好吧,这有一点问题,不是么?下面的代码,也对此进行了说明,注意该示例代码中的类与GreeterIGreeting类型存在于不同的程序集中。

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才能向用户提供一些特定功能,例如从类型中提取接口。一方面,这不是很友好,因为东西很容易崩溃,但是对于客户的价值是毋庸置疑的,所以我们还是选择这样做。

转载于:https://www.cnblogs.com/lwqlun/p/11575686.html

在.NET Core中使用DispatchProxy“实现”非公开的接口相关推荐

  1. 深入源码理解.NET Core中Startup的注册及运行

    开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程序的起点.通过使用Startup,可以配置化处理所有向应用程序所做的请求的管道,同时也可 ...

  2. 源码里没有configure_深入源码理解.NET Core中Startup的注册及运行

    开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程序的起点.通过使用Startup,可以配置化处理所有向应用程序所做的请求的管道,同时也可 ...

  3. .net core 中的[FromBody]

    一.针对.net core中post类型的api注意的地方(前提是Controller上加[ApiController]特性).默认是这个. 1.如果客户端Content-Type是applicati ...

  4. .net使用httpclient获取http状态码_在 .NET Core 中结合 HttpClientFactory 使用 Polly(中篇)...

    译者:码农老王 作者:Polly 团队 原文:http://t.cn/EhZ90oq 声明:我翻译技术文章不是逐句翻译的,而是根据我自己的理解来表述的(包括标题).其中可能会去除一些不影响理解但本人实 ...

  5. ASP.NET Core中GetService()和GetRequiredService()之间的区别

    上篇文章<在.NET Core 3.0中的WPF中使用IOC图文教程>中,我们尝试在WPF中应用.NET Core内置的IOC进行编程,在解析MainWindow的时候我用了GetRequ ...

  6. ASP.NET Core 中文文档 第三章 原理(12)托管

    原文:Hosting 作者:Steve Smith 翻译:娄宇(Lyrics) 校对:何镇汐.许登洋(Seay) 为了运行 ASP.NET Core 应用程序,你需要使用 WebHostBuilder ...

  7. 在asp.net core中使用托管服务实现后台任务

    在业务场景中经常需要后台服务不停的或定时处理一些任务,这些任务是不需要及时响应请求的. 在 asp.net中会使用windows服务来处理. 在 asp.net core中,可以使用托管服务来实现,托 ...

  8. 在 ASP.NET Core 中集成 Skywalking APM

    前言 大家好,今天给大家介绍一下如何在 ASP.NET Core 项目中集成 Skywalking,Skywalking 是 Apache 基金会下面的一个开源 APM 项目,有些同学可能会 APM ...

  9. .ASP NET Core中缓存问题案例

    本篇博客中,我将描述一个关于会话状态(Session State)的问题, 这个问题我已经被询问了好几次了. 问题的场景 创建一个新的ASP.NET Core应用程序 一个用户在会话状态中设置了一个字 ...

最新文章

  1. 一生中用来开会的时间,你知道有多久吗?
  2. ExpandableListView 里面嵌套GridView实现高度自适应
  3. MyBatisPlus3.x中使用代码生成器(全注释)
  4. Taro+react开发(11)--不能加分号
  5. 基于jsp的失物招领系统_基于Java web的校园失物招领系统
  6. 8路抢答器c语言程序,多路抢答器c程序(原创)
  7. 工业和信息化部教育考试中心职业技术证书有必要考吗?
  8. Java 操作Word书签(二):添加文本、图片、表格到书签内容
  9. Android Studio快捷键设置 (实现原eclipse ctrl+m 代码全屏的效果)
  10. MCS-51单片机指令系统总结(自学笔记)
  11. 计算机用户接入最快的,行测真题_2013-2017年固定互联网宽带接入用户数的年增长速度最快的年份是...
  12. 一篇文章看懂Facebook和新浪微博的智能FEED
  13. 实现图片文字识别的方法有哪些
  14. 计算View中的子View在View的superview中的坐标
  15. 方舟:生存进化官服和私服区别
  16. HTML Canvas 涂鸦
  17. dubbo中标签的使用
  18. linux查看操作系统版本的命令
  19. c 语言怎么实现可视化编程,自定义编程语言的实现
  20. 动态┃彼岸花资本、加密资本等宣布与Filenet达成战略合作,并参与Filenet打包节点竞选

热门文章

  1. windows上运行MapReduce出错(Failed to set permissions of path)
  2. 【IT笔试面试题整理】反转链表
  3. 【Tensorflow】tf.set_random_seed(seed)
  4. 关于keil环境的 三个红点(备忘)
  5. pca 主成分分析_六分钟的主成分分析(PCA)的直观说明。
  6. 查看-增强会话_会话式人工智能-关键技术和挑战-第2部分
  7. 深入浅出SQL(1)
  8. 分类算法——决策树(1)
  9. Linux日志系统分析:rsyslog、syslog和klog
  10. 理财平台频繁暴雷,羊毛党该要本金还是撸利息?