title: 分析器:常見問題 date: 2022-04-03 tags: - C# - .NET - Roslyn 前言 源生成器(增量生成器)由於它特殊的定位,關於它的調試十分困難。在這裡分享一些調試它的經驗。 另外經常有寫類庫,然後提供可以生成代碼的Attribute給用戶的需求,此時需要用 ...
title: 分析器:常見問題
date: 2022-04-03
tags:
- C#
- .NET
- Roslyn
前言
源生成器(增量生成器)由於它特殊的定位,關於它的調試十分困難。在這裡分享一些調試它的經驗。
另外經常有寫類庫,然後提供可以生成代碼的Attribute給用戶的需求,此時需要用到傳遞引用的知識點。
調試源生成器
源生成器執行時間
源生成器項目和普通的項目不同。
普通的會在你按下運行或調試後才會運行;而源生成器會在兩種情況下運行:
重新生成解決方案或該項目時候運行,運行後會生成dll文件。在下一次啟動VS的時候,會連著dll一起讀取,所以可能會有VS找不到生成的文件導致報錯,但可以正常運行的問題,重啟VS即可。
在生成項目後第二次及以後打開項目時,每次對代碼進行更改都會重新運行源生成器的dll,並實時將生成的代碼加入到項目中。所以源生成器的執行效率很大程度關乎用戶的編程手感。
以下程式段預設引用命名空間:
using System.Diagnostics;
啟動調試器
在源生成器項目中,直接在Visual Studio用滑鼠點擊行號左邊打的(紅色圓形的)斷點是沒有用的,需要添加一條Debugger.Launch();
,表示啟動了調試器。
如果程式運行到這條語句時,會彈出一個視窗,選擇調試的程式:
建議選擇自己的項目(在此圖的第二個)即可。
點擊OK後程式會停在Debugger.Launch();
處,此時可以插入Debugger.Break();
或直接滑鼠點擊插入斷點。
如果要啟動調試器,Debugger.Launch();
一定要放在源生成器剛開始的位置,而且不要插入多個,尤其不能插在多次執行的程式塊內(如迴圈);若有需要可在其中打斷點,否則第二次打開VS會一直彈窗。
生成中關閉調試器
有時運行一半時發現問題無需繼續調試時,需要關閉調試器。但簡單地終止調試可能無效,因為可能遇到另一個Debugger.Launch();
。
所以我們需要先停止生成,再關閉調試器。
-
生成→取消
-
(如果調試器沒有關閉)調試→停止調試
關閉Visual Studio前
關閉之前我們應該先把Debugger.Launch();
刪除或註釋掉,並重新生成項目,以免下次打開VS的時候自動彈出調試器視窗。
類庫中分析器的傳遞引用
從引用項目時的方式就可以看出,生成器項目本身是作為分析器(Analyzer)項目引入的:
<ItemGroup>
<ProjectReference Include="XXX.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
此時類庫項目引用了源生成器項目,類庫項目又被用戶項目引用,那麼問題是用戶項目可以被源生成器生成代碼嗎?
答案一般是不能。
如果要實現這種效果,那需要滿足兩個條件:
-
類庫項目應作為NuGet包被引用,而非項目引用。
-
類庫項目將分析器包含進NuGet包。
綜上,我們應該在生成NuGet包的項目內這樣寫:
<ItemGroup>
<ProjectReference Include="XXX.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<None Include="XXX.SourceGenerator.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
其中XXX.SourceGenerator.dll是源生成器項目的輸出文件路徑。
在引用該NuGet包之後,除了可以使用生成器外,其他該NuGet項目引用的類型也可以訪問,這就是所謂“傳遞引用”。
其中ReferenceOutputAssembly="false"
是指雖然引用分析器的輸出,但不引用他的類型(如class XXXGenerator
)。
註:如果項目沒有使用NuGet包的必要,並且可以實現項目引用,又有此類需求;則可以簡單地讓用戶項目按分析器引用源生成器項目即可。
分析器簡介
本文分析器主要指以“Analyzer”模式引用的項目或NuGet包,例如源生成器(source generator)、代碼分析器(analyzer)、代碼修複器(codefixer)等
註:其中源生成器和代碼分析器的“血緣關係”更近,可能因為都是.NET使用的分析器,而代碼修複器是提供給Visual Studio使用的,所以關係疏遠一些
分析器引用別的項目時的問題
根據MSDN和Visual Studio自帶的代碼示例,所有分析器和源生成器都必須包含以下兩個庫:
Microsoft.CodeAnalysis.CSharp
Microsoft.CodeAnalysis.Analyzers
所有代碼修複器都必須包含以下這個庫:
Microsoft.CodeAnalysis.CSharp.Workspaces
肯定不止我一個人(bushi)想到,為什麼不把一些最簡單的類型、或者一些工具庫給所有的分析器共用呢?這樣引用時就不需要寫字元串而是nameof(xxx)
了,多優雅。
可惜由於分析器項目的限制,除了以上說的必須包含的庫,其他庫都不能引用。但這也不是說每次寫分析器都要手動實現.NET的新特性,Sergio Pedri[1]大佬實現了Poly#[2](PolySharp)庫,這個庫實現了絕大部分可以實現的特性,並且可以被分析器項目“引用”。因為它是把所有需要的代碼生成到你的項目里,而非直接引用,所以繞過了分析器不能引用項目的限制。
引用這個庫的方法和其他庫一樣,在NuGet里下載並添加包引用即可。
目標平臺問題
分析器項目與普通項目不同,分析器本身的dll不會被用戶項目直接引用,而是被編譯平臺(如.NET SDK或Visual Studio等)引用,從而對代碼進行分析。所以分析器本身的dll不會被包含進用戶項目生成的應用里(源生成器生成的代碼會)
既然分析器的dll是被編譯平臺引用而非用戶項目,那源生成器的目標平臺就應該與編譯平臺有關,而非用戶項目。例如,在x64架構的電腦上編譯x86的應用程式,此時一般的用戶項目目標平臺都應該設置為x86,但分析器項目的目標平臺卻應該與電腦保持一致(x64),因為編譯項目時,是電腦上x64的.NET SDK調用了分析器項目。
為了保證項目編譯的可移植性,如何判斷編譯電腦的架構並設置為對應平臺呢?此時簡單地將目標平臺設為AnyCPU即可,AnyCPU正是根據自身電腦架構獲取的目標平臺。
又如使用GitHub Action時,分析器項目經常會遇到如下錯誤(CS8034):
CSC : warning CS8034: Unable to load Analyzer assembly D:\a\xxx.SourceGenerator\bin\x86\Debug\netstandard2.0\xxx.SourceGenerator.dll : Unable to load xxx.SourceGenerator [D:\a\xxx.csproj]
這顯然是因為目標平臺設置錯誤,但使用Visual Studio手動編譯就沒有問題,sln文件里的平臺映射(platform map)也確實是AnyCPU啊?這是因為解決方案的平臺映射不一定會被所有地方遵循,最穩妥的方案是在csproj文件里指定目標平臺。故在分析器項目中加上如下一行(PlatformTarget
)即可:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
...
<PlatformTarget>AnyCPU</PlatformTarget>
...
</PropertyGroup>
...
</Project>
不過為了直接運行也不出錯,建議平臺映射里的AnyCPU也要保留。
IDE相容性問題
既然分析器是被編譯平臺調用,那分析器的Roslyn版本也要與編譯平臺一致。
所有源生成器和代碼分析器都會引用以下的庫。
Microsoft.CodeAnalysis.CSharp
而代碼修複器則會使用
Microsoft.CodeAnalysis.CSharp.Workspaces
這兩個庫的版本都和Roslyn的版本相同(截至本文發佈,是4.6.0),而Visual Studio的版本也是相關(VS2022的版本目前是VS 17.6.xxx)。所以如果使用了4.6.0的Roslyn,低於17.6.xxx版本的VS就無法使用了。例如VS2022的版本號是VS 17.xxx,VS2019的版本號是VS 16.xxx。如果編寫的分析器需要有較為廣泛的相容性,可能需要降低版本,放棄新特性
分析器的調試方式
分析器在生成(運行)時,預設不會啟動調試器,所以需要手動添加:
Debugger.Launch();
推薦重新生成時採用Debug模式,Release模式會優化代碼,導致有些地方可能看不到需要的變數值。
代碼分析器的調試方法和源生成器一樣,但代碼修複器就有些不同了,添加啟動調試器代碼並重新生成後,需要先重啟VS,這樣VS才會重新載入代碼生成器(然而添加啟動調試器重啟後再修改代碼修複器代碼或者刪除啟動調試器,都不需要重啟VS,只要重新生成一下即可,十分奇怪)。
然後將滑鼠放到代碼分析器提供的警告上,他會出現修複錯誤的提示(此處兩條相同的提示一條來自於VS本身,一條來自於Resharper,並不是BUG):
點擊顯示可能的修補程式後,這個框會一閃而過:
然後跳出選擇調試器的對話框,註意此處與源生成器或代碼分析器不同,此處沒有該解決方案本身而是別的打開的解決方案(如果有的話)或者新的VS實例,任選一個即可。我們點開後就可以開心地進行調試了。
目標框架問題
所有分析器項目的目標框架都必須是.NET Standard2.0。據說因為是VS正在由.NET Framework向.NET (Core)遷移,所以使用這個折衷的方式來相容兩方。如果有朝一日VS完全用.NET重寫的話,我們就能用上完全版的分析器了。
感謝
感謝Huo Yaoyuan大佬[3]的無私耐心解答x,學習時參考了呂毅[4]大佬的博客