在.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.pswp.cn/news/248071.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/248071.shtml
英文地址,請注明出處:http://en.pswp.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&…