在.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#实现一个接口很简单 - 但是如何实现一个通过反射提取到的接口?好吧,这有一点问题,不是么?下面的代码,也对此进行了说明,注意该示例代码中的类与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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/248071.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ajax — 评论列表

<body style"padding: 15px;"><!-- 评论面板 --><div class"panel panel-primary"><div class"panel-heading"><h3 class"panel-title">发表评论</h3></div><form class"panel-bod…

VS2013秘钥

Visual Studio Ultimate 2013 KEY&#xff08;密钥&#xff09;&#xff1a;BWG7X-J98B3-W34RT-33B3R-JVYW9Visual Studio Premium 2013 KEY&#xff08;密钥&#xff09;&#xff1a;FBJVC-3CMTX-D8DVP-RTQCT-92494Visual Studio Professional 2013 KEY&#xff08;密钥&…

Swift傻傻分不清楚系列(七)控制流

本页包含内容&#xff1a; For-In 循环While 循环条件语句控制转移语句&#xff08;Control Transfer Statements&#xff09;提前退出检测 API 可用性 Swift提供了多种流程控制结构&#xff0c;包括可以多次执行任务的while循环&#xff0c;基于特定条件选择执行不同代码分支…

java课程之团队开发冲刺1.8

一.总结昨天进度 1.初步实现用户交互 增删课程表 二.遇到的困难 1.主界面一段程序一直报错 三.今天的任务 1.解决报错问题&#xff0c; 编写查询空教室功能 照片 燃尽图 转载于:https://www.cnblogs.com/qfsr/p/10873636.html

Ajax — 聊天机器人演示

<body><div class"wrap"><!-- 头部 Header 区域 --><div class"header"><h3>小思同学</h3><img src"img/person01.png" alt"icon" /></div><!-- 中间 聊天内容区域 --><div…

uni-app开发微信小程序的几天时间

人只有在不断的学习&#xff0c;才能不断的给自己充电&#xff0c;如果我们停止了学习&#xff0c;就像人没有了血脉&#xff0c;就会死亡&#xff0c;近来学习比较忙&#xff0c;压力比较大&#xff0c;整天面对着电脑&#xff0c;敲击代码&#xff0c;从中虽然收获了快乐&…

Swift傻傻分不清楚系列(八)函数

本页包含内容&#xff1a; 函数定义与调用&#xff08;Defining and Calling Functions&#xff09;函数参数与返回值&#xff08;Function Parameters and Return Values&#xff09;函数参数名称&#xff08;Function Parameter Names&#xff09;函数类型&#xff08;Funct…

Ajax — 第三天

Ajax-03 模板引擎原理 正则回顾 区分正则方法和字符串方法 正则方法 test()exec() 字符串方法 match()replace()split()search() 正则方法由正则表达式调用&#xff1b;字符串方法由字符串调用&#xff1b; exec方法 功能&#xff1a;使用正则表达式匹配字符串&#xff0c…

d3.js 共享交换平台demo

今天在群里遇到一张图 遂来玩一玩&#xff0c;先来上图!! 点击相应按钮&#xff0c;开关线路&#xff0c;此项目的重点是计算相应图形的位置&#xff0c;由于是个性化项目就没有封装布局。好了直接上代码。 <!DOCTYPE html> <html lang"en"> <head&g…

Java知识系统回顾整理01基础05控制流程07结束外部循环

一、break是结束当前循环 二、结束当前循环实例 break; 只能结束当前循环 public class HelloWorld { public static void main(String[] args) { //打印单数 for (int i 0; i < 10; i) { for (int j 0; j < 1…

Swift傻傻分不清楚系列(九)闭包

本页包含内容&#xff1a; 闭包表达式&#xff08;Closure Expressions&#xff09;尾随闭包&#xff08;Trailing Closures&#xff09;值捕获&#xff08;Capturing Values&#xff09;闭包是引用类型&#xff08;Closures Are Reference Types&#xff09;非逃逸闭包(Nones…

Ajax — 新闻列表

注意&#xff1a;本项目主要利用到了template&#xff0c;模板引擎进行编写 模板引擎代码下载地址 <div id"news-list"><!-- 这里放数据 --></div>.news-item {display: flex;border: 1px solid #eee;width: 700px;padding: 10px;margin-bottom: …

vim下更省心地用中文

在vim下使用中文是个麻烦。除了写代码&#xff0c;很多时候也需要做笔记。以下介绍rime输入法的一个功能&#xff0c;它可以减少vim下中文输入带来的麻烦。在***.custom.yaml下添加代码&#xff1a; "key_binder/bindings": - { when: always, accept: ReleaseEs…

Python 常见的内置模块

1. abs() 函数 描述 abs() 函数返回数字的绝对值 #!/usr/bin/pythonprint "abs(-45) : ", abs(-45) print "abs(100.12) : ", abs(100.12) print "abs(119L) : ", abs(119L)以上实例运行后输出结果为&#xff1a;abs(-45) : 45 abs(100.12) : …

Ajax — 第四天

数据交换格式 XML 写法&#xff1a; 一个文档有且只有一个根标签标签必须闭合属性值必须加引号 如果说服务器返回的数据是xml格式的 前端需要把服务器返回的xml当做document对象来处理目前无法演示&#xff0c;自己写接口的时候&#xff0c;我们可以测试一下。 JSON 写法…

检测字符串包含emoji表情

有时候在开发时会遇到不希望字符串中包含emoji表情的情况&#xff0c;Google之后发现了方法&#xff0c;但是似乎iOS9之后的emoji无法过滤&#xff0c;继续寻找方法&#xff0c;在一个NSString的扩展中发现了办法 #import <Foundation/Foundation.h>/**Category to searc…

数据库系统原理(第三章数据库设计 )

一、数据库设计概述 数据库的生命周期 数据库设计的目标&#xff1a; 满足应用功能需求&#xff08;存、取、删、改&#xff09;&#xff0c;良好的数 据库性能&#xff08;数据的高效率存取和空间的节省 共享性、完整性、一致性、安全保密性&#xff09;数据库设计的内容 数据…

Ajax — 第五天

Ajax-05 xhr&#xff08;level-2&#xff09;新特性 responseType属性和response属性 responseType: 表示预期服务器返回的数据的类型 “” &#xff0c;默认空text&#xff0c;和空一样&#xff0c;表示服务器返回的数据是字符串格式json&#xff0c;表示服务器返回的是js…

java 根据身份证号码获取出生日期、性别、年龄

1.情景展示 如何根据身份证号&#xff0c;计算出出生日期、性别、年龄? 2.解决方案 从网上找的别人的&#xff0c;因为并没有实际用到&#xff0c;所以并未对其优化&#xff01; /*** 通过身份证号码获取出生日期、性别、年龄* param certificateNo* return 返回的出生日期格式…

Swift傻傻分不清楚系列(十)枚举

本页内容包含&#xff1a; 枚举语法&#xff08;Enumeration Syntax&#xff09;使用 Switch 语句匹配枚举值&#xff08;Matching Enumeration Values with a Switch Statement&#xff09;关联值&#xff08;Associated Values&#xff09;原始值&#xff08;Raw Values&…