最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

探索 C# 9 的源代碼生成器

2021-01-27 22:46 作者:SunnieShine  | 我要投稿

源代碼生成器(Source Generator)是一種機(jī)制,它允許我們使用 C# 代碼在編譯前產(chǎn)生別的、自定義的代碼,以達(dá)到代碼注入的效果。


何為代碼注入?

那什么是代碼注入(Code Injection)呢?我舉一個(gè)稍微偏一點(diǎn)但是比較形象的例子。在多個(gè)人比賽之前,裁判就已經(jīng)定下了一些比賽期間的“黑幕”:比如我給誰(shuí)誰(shuí)誰(shuí)比賽選手做一個(gè)標(biāo)記,裁判在比賽之前要是看到了這個(gè)標(biāo)記,裁判就知道我給他說(shuō)過(guò),讓裁判故意判他贏。

這個(gè)例子里,標(biāo)記好比是編譯前用戶(hù)寫(xiě)代碼時(shí)給的特性之類(lèi)的類(lèi)型標(biāo)記,而裁判就是這個(gè)編譯器。在編譯程序的時(shí)候,一旦發(fā)現(xiàn)這個(gè)東西,就立刻作出一些額外的補(bǔ)充。我再舉個(gè)代碼層面的例子:

假如我有這么一個(gè)類(lèi)型 %5Ctext%7BPoint%7D,這個(gè)類(lèi)型我不給出任何的實(shí)現(xiàn),但給了一個(gè)特性標(biāo)記:%5Ctext%7BTranslateIntoRecordsAttribute%7D。是的,這個(gè)特性是我隨手寫(xiě)的,庫(kù)文件里不存在。假定這個(gè)特性用來(lái)表示,讓編譯器知道,我一會(huì)兒要自動(dòng)追加一個(gè)?%5Ctext%7BX%7D?一個(gè) %5Ctext%7BY%7D?這兩個(gè)屬性到項(xiàng)目里,并自動(dòng)實(shí)現(xiàn)諸如 %5Ctext%7Boperator%20%3D%3D%7D、%5Ctext%7Boperator%20!%3D%7D、%5Ctext%7BEquals%7D%5Ctext%7BGetHashCode%7D?之類(lèi)的方法,而我不從這里直接實(shí)現(xiàn)(因?yàn)閷?xiě)起來(lái)比較復(fù)雜,我懶得寫(xiě),于是就想使用代碼注入的方式,把工作交給編譯器)。

再舉個(gè)例子。假如,我實(shí)現(xiàn)了一個(gè)類(lèi)型,這個(gè)類(lèi)型底層用了一個(gè)數(shù)組來(lái)封裝,比如這樣:

可以發(fā)現(xiàn)一個(gè)顯著的問(wèn)題。索引器 %5Ctext%7Bthis%5Bint%5D%7D?的參數(shù)可能隨用戶(hù)傳入負(fù)數(shù)進(jìn)去。但我在實(shí)現(xiàn)的時(shí)候并沒(méi)有寫(xiě)這句話。我可以考慮使用代碼注入的方式,給 getter 和 setter 標(biāo)記特性(或者直接給屬性標(biāo)記諸如 %5Ctext%7BParamInRangeAttribute%7D),以表示參數(shù)必須在合理的范圍內(nèi),否則拋出異常。這樣我也懶得寫(xiě)這些代碼了,也可以防止我有些時(shí)候代碼寫(xiě)錯(cuò)了或者漏寫(xiě)了:

然后寫(xiě)上合適的生成代碼,最后編譯器可以自動(dòng)加上注入代碼:

當(dāng)然,這我們壓根沒(méi)給出實(shí)現(xiàn)這樣的行為的代碼,我也就這么說(shuō)說(shuō),讓你明白這個(gè)代碼注入到底有多強(qiáng)。


源代碼生成器和 T4 模板

下面我們回到話題,來(lái)說(shuō)一下,如何構(gòu)造一個(gè)源代碼生成器。

前文說(shuō)到,源代碼生成器的作用之一,就是用來(lái)代碼注入。接下來(lái)我們?yōu)榱俗尨蠹胰腴T(mén),我們來(lái)說(shuō)另外一種用法:代替 T4 模板。

前文的內(nèi)容還是對(duì)入門(mén)的朋友們來(lái)說(shuō)太難了,所以這里我們講簡(jiǎn)單一點(diǎn)。如果你想學(xué)習(xí)前文的代碼注入的過(guò)程,你可以參考 Source Generator Cookbook 一節(jié)的內(nèi)容,但有點(diǎn)多,而且全英文,所以需要你多多努力。

地址:https://github.com/dotnet/roslyn/blob/master/docs/features/source-generators.cookbook.md

T4 模板是一種控制復(fù)雜的、生成類(lèi)似變長(zhǎng)泛型參數(shù)這種無(wú)法從 C# 語(yǔ)言本身做到,但又有規(guī)律性的代碼。當(dāng)然,我知道 C++ 里已經(jīng)有了變長(zhǎng)泛型參數(shù)這個(gè)概念和特性了,但 C# 官方不出意外是不會(huì)添加這個(gè)特性了,因?yàn)闆](méi)有必要。T4 模板可以生成這樣的代碼。那么做法很簡(jiǎn)單,按照 T4 模板的語(yǔ)法規(guī)則,將基本的代碼的文本寫(xiě)出來(lái),把變化的部分用 %5Ctext%7B%3C%25%3D%20%25%3E%7D、%5Ctext%7B%3C%25%20%25%3E%7D?這樣的標(biāo)記來(lái)替換掉。但是,這樣的代碼控制縮進(jìn)非常困難。稍不注意,標(biāo)記位置寫(xiě)得不對(duì),就會(huì)導(dǎo)致代碼生成的縮進(jìn)有問(wèn)題,而且就算是沒(méi)問(wèn)題了,T4 模板代碼本身也會(huì)很丑。

那么,源代碼生成器就這么誕生了。


使用源代碼生成器

下面我們來(lái)介紹一下源代碼生成器是怎么用的。

第一步:創(chuàng)建動(dòng)態(tài)鏈接庫(kù)項(xiàng)目

怎么用呢?我們先創(chuàng)建一個(gè)生成動(dòng)態(tài)鏈接庫(kù)的項(xiàng)目。

在此之前,請(qǐng)確保你的 Visual Studio 是 2019 v16.9 Preview 3 及其以上版本的,因?yàn)閺倪@個(gè)版本開(kāi)始,Visual Studio 才開(kāi)始支持源代碼生成器的使用和顯示。

如果不使用 16.9 Preview 3 及其以上的版本運(yùn)行和編譯程序的話,你將 100% 獲得編譯器警告 CS8032 一份。

老實(shí)說(shuō),這個(gè)功能才出來(lái)不久,所以很多 bug 需要修復(fù)。比如第一次運(yùn)行的時(shí)候,可能報(bào)的 CS8032 警告不論編譯多少次程序,不論編譯是不是已經(jīng)成功過(guò),這個(gè)編譯器警告一直都存在(說(shuō)白了就是不會(huì)刷新),只有重啟了才沒(méi)有;但是隔一會(huì)兒又來(lái)了,但是運(yùn)行編譯程序則又是成功的。

1-1

然后選擇“Class Library”,點(diǎn)“Next"。

1-2

這里是讓你創(chuàng)建一個(gè)項(xiàng)目,給項(xiàng)目取名。項(xiàng)目隨便取名,比如 %5Ctext%7BSourceGenerator%7D?就好。也繼續(xù)點(diǎn)擊“Next”。

1-3

最后,注意這一步。請(qǐng)選擇 .NET Standard 2.0。請(qǐng)不要選擇 .NET 或 .NET Core、.NET Framework 或其它內(nèi)容(也不要選擇 .NET Standard 2.1),這一點(diǎn)很重要。

第二步:創(chuàng)建一個(gè)生成器的類(lèi),并且實(shí)現(xiàn) `ISourceGenerator` 接口和標(biāo)記 `GeneratorAttribute` 特性

當(dāng)我們創(chuàng)建完成了基本的項(xiàng)目后,項(xiàng)目會(huì)默認(rèn)帶一個(gè)空的類(lèi) %5Ctext%7BClass1%7D。這個(gè)時(shí)候隨便改個(gè)名字就行,比如叫 %5Ctext%7BHelloWorldGenerator%7D。

這個(gè)時(shí)候,請(qǐng)寫(xiě)這樣的代碼:

2-1

代碼上會(huì)報(bào)錯(cuò)。很明顯的原因:%5Ctext%7BGeneratorAttribute%7D?和 %5Ctext%7BISourceGenerator%7D?并不存在。這個(gè)時(shí)候我們點(diǎn)擊彈出的黃色燈泡圖標(biāo)(或者按 %5Ctext%7BAlt%2BEnter%7D),可以彈出如下的內(nèi)容:

2-2

此時(shí),請(qǐng)選擇“Install package 'Microsoft.CodeAnalysis.Common'”就可以了。然后等待 VS 自動(dòng)安裝。

安裝完成后,接口和特性就都會(huì)成功導(dǎo)入。但是,此時(shí)會(huì)有這樣的錯(cuò)誤信息:

2-3

當(dāng)然,因?yàn)槲覀儧](méi)實(shí)現(xiàn)接口呢。然后我們照著這個(gè)實(shí)現(xiàn)接口。

這樣,接口就不報(bào)錯(cuò)了。請(qǐng)注意代碼里寫(xiě)的這個(gè)注釋。這是我加的,因?yàn)槲覀円粫?huì)兒只在 %5Ctext%7BExecute%7D?方法里給實(shí)現(xiàn),而不是在 %5Ctext%7BInitialize%7D?方法里。下面這個(gè)方法我們這一次用不到,留空就行。

留空不是拋 %5Ctext%7BNotImplementedException%7D。留空就是保持空代碼塊就行,不寫(xiě)代碼,而不是拋異常。

第三步:照著我抄就行了,看我怎么寫(xiě)代碼的

是的,看標(biāo)題就明白我的意思了:

行了。抄上去就行。

第四步:創(chuàng)建一個(gè)控制臺(tái)程序項(xiàng)目

接著,創(chuàng)建一個(gè)測(cè)試項(xiàng)目。當(dāng)然了,這個(gè)測(cè)試項(xiàng)目那肯定得是可以運(yùn)行的,對(duì)吧。

4-1

這里隨便取名。

4-2

然后是這里??刂婆_(tái)程序可以是任何版本的,只要不低于 .NET Standard 2.1(就是剛才第一個(gè)項(xiàng)目里,你選擇的那一項(xiàng))。

點(diǎn)擊“Create”就行了。

第五步:修改項(xiàng)目配置文件

這里是整個(gè)創(chuàng)建過(guò)程的難點(diǎn)。請(qǐng)打開(kāi)這個(gè)新建項(xiàng)目的配置文件(%5Ctext%7B*.csproj%7D?的那個(gè)文件)。然后添加這樣的內(nèi)容:

5-1

現(xiàn)在,配置文件長(zhǎng)這樣。然后關(guān)閉這個(gè)文件。記得保存,而且別寫(xiě)錯(cuò)了。

然后隔一會(huì)兒,你就可以發(fā)現(xiàn),你剛添加的這個(gè)源代碼生成器項(xiàng)目已經(jīng)添加到了測(cè)試項(xiàng)目里了,而且是以一個(gè)分析器的形式添加進(jìn)來(lái)的(看顯示的位置,是在 Dependencies 的 Analyzers 下面的。這倆單詞啥意思呢?依賴(lài)和分析器的意思)。

5-2

就這個(gè)顯示分析器的功能,只有 16.9 Preview 3 才開(kāi)始有的。這也就是為什么我告訴你必須要這個(gè)版本及其以上的才可以的真正原因:分析器都不顯示,你從哪里查看生成器和源代碼呢?

第六步:更替代碼

這里就需要我們更新源代碼了。我們把剛創(chuàng)建的控制臺(tái)的程序的文件改成這樣:

是的,就這兩句話。這是 C# 9 提供的新特性:全局 %5Ctext%7BMain%7D?方法。當(dāng)你只寫(xiě)了執(zhí)行邏輯的時(shí)候,編譯器會(huì)自動(dòng)創(chuàng)建一個(gè) %5Ctext%7BProgram%7D?類(lèi),并帶一個(gè) %5Ctext%7BMain%7D?方法;然后這里的執(zhí)行代碼就是被一起塞進(jìn) %5Ctext%7BMain%7D?方法里的代碼了。這個(gè)方法自帶一個(gè) %5Ctext%7Bstring%5B%5D%20args%7D?參數(shù)。所以你壓根不用擔(dān)心你寫(xiě)的代碼會(huì)出問(wèn)題,或者沒(méi)辦法用命令行參數(shù)。

當(dāng)然,這里不是講 C# 新語(yǔ)法的。寫(xiě)完這段代碼的時(shí)候,可能你會(huì)看到報(bào)錯(cuò)信息:提示你 %5Ctext%7BGeneratedNamespace%7D?命名空間不存在啊,或者 %5Ctext%7BGeneratedClass%7D?這個(gè)類(lèi)也不存在啊,這樣類(lèi)似的錯(cuò)誤。

6-1

別擔(dān)心,你的源代碼生成器已經(jīng)完成了,所以代碼一會(huì)兒會(huì)自動(dòng)在編譯的時(shí)候生成,因此它一會(huì)兒就是存在的了。

第七步:然后重啟 Visual Studio

這一步應(yīng)該是目前 Source Generator 和 Visual Studio 2019 交互的 bug。我不知道以后 VS 會(huì)不會(huì)修復(fù)這一個(gè)問(wèn)題,但目前必須要這么做才能運(yùn)行成功。

請(qǐng)重啟 VS。

最后一步:運(yùn)行和調(diào)試代碼

在前文描述的過(guò)程里,我們已經(jīng)大概表達(dá)了所有需要編譯和運(yùn)行一個(gè)帶有源代碼生成器的程序,到底應(yīng)該怎么寫(xiě)東西。現(xiàn)在是最后一步了。那自然是調(diào)試代碼。

前文如果嘗試完成后,發(fā)現(xiàn)依舊失敗的話,請(qǐng)嘗試把代碼生成器項(xiàng)目的標(biāo)準(zhǔn)改成 .NET Standard 2.0。.NET Standard 2.1 可以用,但可能第一次需要 2.0 版本的標(biāo)準(zhǔn)進(jìn)行編譯才可以成功。

8-1

這個(gè)時(shí)候,你就會(huì)發(fā)現(xiàn),類(lèi)名已經(jīng)不再帶有紅色波浪線了,而是正確的類(lèi)的語(yǔ)義著色,這就說(shuō)明,這個(gè)類(lèi)成功編譯出來(lái)了。

我們可以點(diǎn)進(jìn)去看看。

8-2

你將獲得原本我們剛才寫(xiě)的那串代碼,最后生成的東西。

上面的文字說(shuō):“這個(gè)文件是被 %5Ctext%7BSourceGenerator.HelloWorldGenerator%7D?這個(gè)生成器自動(dòng)生成的,且你無(wú)法對(duì)其進(jìn)行修改”。

下面我們來(lái)運(yùn)行一下。

很高興,我們得到了想要的結(jié)果。

8-3

那么,整個(gè)項(xiàng)目我們就完成了。

總結(jié)

源代碼生成器的主要作用是用來(lái)在編譯前注入別的代碼(產(chǎn)生代碼)來(lái)達(dá)到額外的、必須在編譯前要完成的功能。源代碼生成器有些時(shí)候格外重要,但不能亂用。請(qǐng)勿濫用代碼注入。

另外,我查了很多的資料,至于我們能否改成現(xiàn)在出的 .NET 5,很抱歉,不能。

可以參考這個(gè)回答:https://stackoverflow.com/a/65480017/13613782(原問(wèn)題是說(shuō),我想把 .NET Standard 2.0 這個(gè)設(shè)定改成 .NET 5,可以嗎)。

探索 C# 9 的源代碼生成器的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
铁力市| 洞头县| 德安县| 历史| 固原市| 调兵山市| 金乡县| 高安市| 塘沽区| 阿克苏市| 阿拉善右旗| 兴安盟| 上思县| 博白县| 云和县| 徐闻县| 依兰县| 三穗县| 台安县| 纳雍县| 乐安县| 荆门市| 阿勒泰市| 久治县| 平果县| 从江县| 秦皇岛市| 自治县| 泰兴市| 茂名市| 花垣县| 泾源县| 玛沁县| 开阳县| 沙湾县| 沁阳市| 渝北区| 胶州市| 冕宁县| 边坝县| 临潭县|