分析器:常見問題

来源:https://www.cnblogs.com/pokersang/Undeclared/17964221
-Advertisement-
Play Games

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();

所以我們需要先停止生成,再關閉調試器。

  1. 生成→取消

  2. (如果調試器沒有關閉)調試→停止調試

關閉Visual Studio前

關閉之前我們應該先把Debugger.Launch();刪除或註釋掉,並重新生成項目,以免下次打開VS的時候自動彈出調試器視窗。

類庫中分析器的傳遞引用

從引用項目時的方式就可以看出,生成器項目本身是作為分析器(Analyzer)項目引入的:

<ItemGroup>
    <ProjectReference Include="XXX.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

此時類庫項目引用了源生成器項目,類庫項目又被用戶項目引用,那麼問題是用戶項目可以被源生成器生成代碼嗎?

答案一般是不能。

如果要實現這種效果,那需要滿足兩個條件:

  1. 類庫項目應作為NuGet包被引用,而非項目引用。

  2. 類庫項目將分析器包含進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):

analyzer

點擊顯示可能的修補程式後,這個框會一閃而過:

codefix

然後跳出選擇調試器的對話框,註意此處與源生成器或代碼分析器不同,此處沒有該解決方案本身而是別的打開的解決方案(如果有的話)或者新的VS實例,任選一個即可。我們點開後就可以開心地進行調試了。

choose_debugger

目標框架問題

所有分析器項目的目標框架都必須是.NET Standard2.0。據說因為是VS正在由.NET Framework向.NET (Core)遷移,所以使用這個折衷的方式來相容兩方。如果有朝一日VS完全用.NET重寫的話,我們就能用上完全版的分析器了。

感謝

感謝Huo Yaoyuan大佬[3]的無私耐心解答x,學習時參考了呂毅[4]大佬的博客


  1. Sergio Pedri ↩︎

  2. Poly# ↩︎

  3. Huo Yaoyuan ↩︎

  4. 呂毅博客 ↩︎


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 相信很多人在初學Python的時候,經常最後作業就是完成一個學生管理系統,但是我們來做一個完美的學生管理系統,並且將數據儲存到資料庫里。 我們先看看我們的資料庫怎麼設置。 首先呢,我選擇用的是SQL Server 然後,我們的資料庫名稱為學生管理系統 接著,新建一張表,我設置表的名稱為學生信息表。 ...
  • Python裝飾器是一種特殊的函數,它接收一個函數作為參數,然後返回一個新的函數,用於擴展或修改原始函數的行為。裝飾器提供了一種便捷的方式來在不修改被裝飾函數源代碼的情況下,增加、修改或包裝函數的功能。通俗點說就是儘量不修改原有功能代碼的情況下,給原有的功能添加新的功能。 裝飾器的基本語法是使用@符 ...
  • 在Spring框架中,事務管理是一個核心功能,然而有時候會遇到事務失效的情況,這可能導致數據一致性問題。本文將深入探討一些Spring事務失效的常見場景,並提供詳細的例子以及解決方案。 1. 跨方法調用問題 場景: 當一個事務方法內部調用另一個方法,而被調用的方法沒有聲明為@Transactiona ...
  • 在 MongoDB 中,我們使用 find() 和 find_one() 方法來在集合中查找數據,就像在MySQL資料庫中使用 SELECT 語句來在表中查找數據一樣 查找單個文檔 要從MongoDB的集合中選擇數據,我們可以使用 find_one() 方法。 find_one() 方法返回選擇中的 ...
  • 【版權聲明】未經博主同意,謝絕轉載!(請尊重原創,博主保留追究權) https://www.cnblogs.com/cnb-yuchen/p/17963363 出自【進步*於辰的博客】 參考筆記一,P3.13、P5.1;筆記三,P43.1/3、P44.1。 註:我暫且沒有整理關於二進位、原碼、補碼和 ...
  • 背景 在應用系統發展的初期,我們並不知道以後會發展成什麼樣的規模,所以一開始不會考慮複雜的系統架構,複雜的系統架構費時費力,開發周期長,與系統發展初期這樣的一個定位是不吻合的。所以,我們都會採用簡單的架構,隨著業務不斷的發展,訪問量不斷升高,我們再對系統進行架構方面的優化。 架構演進 系統建立初期, ...
  • C++ 學習寶藏網站分享 1. C++ 線上參考手冊 Cppreference https://zh.cppreference.com C++ 開發者必備的線上參考手冊,是我最常訪問的 C++ 網站之一。作為參考手冊,不僅包含了語言本身的詞法、語法特性,還包含了對 C++ 標準庫的介紹:需要 inc ...
  • 創建了一個Avalonia控制項庫,添加了一個UserControl進行編輯時,出現了一個奇怪的問題:無法預覽,沒有智能提示,編譯報錯No executable found。設計器顯示:reference the library from an executable or wait for the s ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...