­

.NET CLI簡單教程和項目結構

WHAT IS .NET CLI ?

.NET 命令行介面 (CLI) 工具是用於開發、生成、運行和發布 .NET 應用程式的跨平台工具鏈。


來源:.NET CLI | Microsoft Docs

.NET CLI是.NET官方的一個命令行工具。本文將介紹.NET CLI的幾個主要的命令。並通過這幾個命令來了解.NET控制台程式的項目結構。

不建議在學習階段使用IDE

IDE是一種非常好的編程工具,但是,在初學階段,並不建議使用IDE,因為IDE會讓學習的人喪失對程式第一手的感知,有相當多的人在使用高級的框架開發的時候會出現沒有IDE就不會開發的現象,我對IDE的看法是:「開發需要依賴IDE,但是不能依靠IDE」,也就是說,在沒有IDE的情況下要依然能夠會編寫程式,會運行和發布程式。

沒有IDE,那麼就需要用.NET CLI創建、運行、構建和發布程式,用文本編寫軟體編寫程式。文本編寫軟體可以用系統自帶的記事本,但是更建議使用帶「程式碼高亮和方法跳轉」的文本編寫軟體,比如github開發的Atom或者微軟的vscode

微軟的Visual Studio 2019是一個比較好的.NET開發IDE。它的用法將在後面講到。

事實上,強大的IDE(Visual Studio)在進行構建、編譯和發布程式等操作的時候,依靠的也是.NET CLI

下載並安裝.NET CLI

.NET CLI.NET SDK的一部分,下載.NET CLI的前提是去下載.NET SDK.NET SDK 是一組用於開發和運行 .NET 應用程式的庫和工具。我們一般意義上講的.NET就是指的.NET SDK

.NET 命令行介面 (CLI) 工具是用於開發、生成、運行和發布 .NET 應用程式的跨平台工具鏈。

.NET CLI 附帶了 .NET SDK。 若要了解如何安裝 .NET SDK,請參閱安裝 .NET Core


來源:.NET CLI | Microsoft Docs

如果在CMD輸入dotnet --version之後,有正確的關於dotnet版本的回應的話,那麼就證明.NET SDK已經安裝成功,作為.NET SDK一部分的.NET CLI也已經安裝完畢。

(base) PS C:\Users\蘇月晟\Desktop> dotnet --version
5.0.401

安裝文本編輯器

前面已經講了不建議在學習階段使用高級的IDE,所以推薦一個好用的文本編輯器——Visual Studio Code。簡稱「vscode」。

vscode是微軟的一款開源產品,它帶有程式碼高亮功能,支援豐富的插件,可以直接調用終端,也可以直接在終端裡面通過code命令直接調用vscode。更重要的是微軟官方文檔裡面將.NET CLI的實例講解和vscode的使用放在了一起。

下載安裝完成之後,安裝三個插件,安裝方式是點擊左側的插件按鈕,進行搜索安裝。

三個插件如下:

  • Chinese (Simplified) (簡體中文) Language Pack for Visual Studio Code
  • C#
  • C# XML Documentation Comments

這三個插件,一個是將vscode介面翻譯成中文,一個是為編寫和調試C#程式碼提供支援,最後一個插件是可以自動生成類注釋和方法注釋的模板。

.NET CLI 初級命令

初級命令就是newrunbuildpublish

dotnet new

根據指定的模板,創建新的項目、配置文件或解決方案。

dotnet new <TEMPLATE> [--dry-run] [--force] [-lang|--language {"C#"|"F#"|VB}]
    [-n|--name <OUTPUT_NAME>] [-o|--output <OUTPUT_DIRECTORY>] [Template options]

dotnet new -h|--help

常見的命令是dotnet new console -o <appname>

console是模板的名稱,代表的是控制台程式。可以運行 dotnet new --list 以查看所有已安裝模板的列表。

常用命令選項

  • -o|--output <OUTPUT_DIRECTORY>

    指定的是項目的輸出目錄,同時也代表了項目的名稱。

  • -n|--name <OUTPUT_NAME>

    指定所創建的輸出的名稱。 如果未指定名稱,使用的是當前目錄的名稱。

  • -lang|--language {C#|F#|VB}

    一般情況下默認的語言就是C#

項目結構

假設使用dotnet new console -o MyAppOne創建了一個名為MyAppOne的控制台應用,那麼在沒有經過編譯運行的情況下MyAppOne文件夾下面的結構應該是這樣的:

.
├── MyAppOne.csproj
├── Program.cs
└── obj

每個文件的作用

  • MyAppOne.csproj

    這個文件是項目組織和配置的核心,會在生成時被MSBuild用來作為輸入

  • **obj **

    文件夾包含了編譯與生成時需要的和產生的中間文件,一般不需要關注。

  • Progam.cs

    就是程式的程式碼文件,裡面已經寫了一段最基本的HelloWord程式碼。

csproj 文件的本質是一個保存項目資訊的xml文件。默認配置如下:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

</Project>

項目系統和 MSBuild

.NET 應用是使用 MSBuild 從源程式碼中生成的。 項目文件(.csproj、.fsproj 或 .vbproj)指定目標和負責編譯、打包和發布程式碼的關聯任務 。 有引用目標和任務的標準集合的 SDK 標識符。 使用這些標識符有助於使項目文件較小且易於使用。

關於MSBuild的常用屬性和命令行將在後面講到。

dotnet run

無需任何顯式編譯或啟動命令即可運行源程式碼。

dotnet run [-a|--arch <ARCHITECTURE>] [-c|--configuration <CONFIGURATION>]
    [-f|--framework <FRAMEWORK>] [--force] [--interactive]
    [--launch-profile <NAME>] [--no-build]
    [--no-dependencies] [--no-launch-profile] [--no-restore]
    [--os <OS>] [--project <PATH>] [-r|--runtime <RUNTIME_IDENTIFIER>]
    [-v|--verbosity <LEVEL>] [[--] [application arguments]]

dotnet run -h|--help

dotnet run的本質其實上是編譯並運行。編譯這一步它其實是相當於使用dotnet build,運行這一步相當於運行依賴於框架的應用程式 DLL,比如dotnet MyAppOne.dll

命令取決於生成程式碼的 dotnet build 命令。 對於此生成的任何要求,例如項目必須首先還原,同樣適用於 dotnet run


來源:dotnet run 命令 – .NET CLI | Microsoft Docs

輸出文件會寫入到默認位置,即 bin/<configuration>/<target>。 例如,如果具有 netcoreapp2.1 應用程式並且運行 dotnet run,則輸出置於 bin/Debug/netcoreapp2.1。 將根據需要覆蓋文件。 臨時文件將置於 obj 目錄。

常用命令選項

  • -c|--configuration <CONFIGURATION>

    定義生成配置。 大多數項目的默認配置為 Debug,但你可以覆蓋項目中的生成配置設置。也就是說configuration默認是Debug,運行dotnet run之後的輸出文件會在bin/Debug之下。

  • -f|--framework <FRAMEWORK>

    使用指定框架生成並運行應用。 框架必須在項目文件中進行指定。一般情況下不需要在命令行中指定框架,只要在MSBuild文件(.csproj)文件裡面指定一個<TargetFramework>就可以了,如果該項目指定多個框架,在不使用 -f|--framework <FRAMEWORK> 選項指定框架時,執行 dotnet run 將導致錯誤。也就是說,一般情況下,默認的框架是生成項目的.NET SDK的版本,我安裝的是,NET 5.0,那麼我又沒有更改<TargetFramework>,那麼運行dotnet run之後的輸出文件會在bin/Debug/net5.0之下。

一般情況下,是在項目根目錄運行dotnet run

項目結構

嘗試對在dotnet new一節中生成的控制台項目MyAppOne的根目錄運行dotnet run。會運行默認的Program.cs,輸出Hello World!

項目結構會增加一個bin目錄:

.
├── MyAppOne.csproj
├── Program.cs
├── bin
└── obj

bin目錄的結構如下:

.
└── Debug
    └── net5.0
        ├── MyAppOne.deps.json
        ├── MyAppOne.dll
        ├── MyAppOne.exe
        ├── MyAppOne.pdb
        ├── MyAppOne.runtimeconfig.dev.json
        ├── MyAppOne.runtimeconfig.json
        └── ref
            └── MyAppOne.dll

為什麼執行dotnet run之後的輸出文件會在bin/Debug/net 5.0之下,已經在前面dotnet run的選項里講的很清楚了,就不再贅述。下面講一下輸出文件的每一個文件的作用。

每個文件的作用

  • MyAppOne.deps.json

    保存一個依賴的列表,也就是編譯上下文數據和編譯依賴,不是技術上需要的,但是在使用服務,包快取或共享這些安裝功能的時候是需要的。這個文件由程式進行處理和使用,用戶不應編輯。

  • MyAppOne.dll

    託管程式集,包括符合 ECMA 的入口點令牌,通俗說就是程式本體。運行它需要dotnet MyAppOne.dll

  • MyAppOne.exe

    可執行文件,雙擊就能運行。這個東西應該不用多解釋了,使用過windows的人應該對exe不陌生。

  • MyAppOne.runtimeconfig.json

    包含運行時配置設置的可選配置文件。這個可選可不是說這個文件可有可無,它很重要,甚至缺少他之後甚至無法運行DLL程式,這個可選指的是再這個文件裡面可以對相關配置進行選擇。

  • MyAppOne.pdb

    該文件記錄了程式碼中斷點等調試資訊,沒有它,打的斷點就不好使了,在Visual Studio 2019中有一個Debug模式,調試程式碼時使用Debug模式,打的斷點才能完全發揮作用,否則就只能是告訴開發者出什麼錯了+錯誤在哪行。在vscode裡面調試默認是的就是這個模式。這個文件不應該由用戶編輯。

  • MyAppOne.runtimeconfig.dev.json

    一個可有可無的文件,他的作用是增加附加的運行時探索路徑,dotnet的GitHub的討論上可以看到dotnet官方已經著手取消這個文件(參見Reason for runtimeconfig.dev.json in build output since .NET Core 3? )。因為大於等於.NET Core 3.0的版本,會將 NuGet 中的庫依賴項複製到輸出文件夾, 而不是在運行時從 NuGet 全局包文件夾中對其進行解析。也就是說在.NET 5.0這個版本中,runtimeconfig.dev.json這個文件基本沒有意義。刪除這個文件不會影響運行。

  • ref文件夾

    引用程式集 是一種特殊類型的程式集,僅包含程式集的公共介面的程式集,它們有助於加快構建過程,因為依賴於該程式集的項目將能夠看到沒有理由重新編譯,即使組件的內部發生了變化,因為從外表上看,它仍然是一樣的。如果想取消ref目錄的生成,在MSBuild文件中添加<ProduceReferenceAssembly>false</ProduceReferenceAssembly>即可。

dotnet build

生成項目及其所有依賴項,可以類比為編譯。

dotnet build [<PROJECT>|<SOLUTION>] [-a|--arch <ARCHITECTURE>]
    [-c|--configuration <CONFIGURATION>] [-f|--framework <FRAMEWORK>]
    [--force] [--interactive] [--no-dependencies] [--no-incremental]
    [--no-restore] [--nologo] [--no-self-contained] [--os <OS>]
    [-o|--output <OUTPUT_DIRECTORY>] [-r|--runtime <RUNTIME_IDENTIFIER>]
    [--self-contained [true|false]] [--source <SOURCE>]
    [-v|--verbosity <LEVEL>] [--version-suffix <VERSION_SUFFIX>]

dotnet build -h|--help

輸出文件會寫入到默認位置,即 bin/<configuration>/<target>

常用命令選項

  • -c|--configuration <CONFIGURATION>

    定義生成配置。 大多數項目的默認配置為 Debug,但你可以覆蓋項目中的生成配置設置。也就是說configuration默認是Debug,運行dotnet build之後的輸出文件會在bin/Debug之下。

  • -f|--framework <FRAMEWORK>

    編譯特定框架。 必須在項目文件中定義該框架。一般情況下不需要在命令行中指定框架,只要在MSBuild文件(.csproj)文件裡面指定一個<TargetFramework>就可以了,如果該項目指定多個框架,在不使用 -f|--framework <FRAMEWORK> 選項指定框架時,執行 dotnet run 將導致錯誤。也就是說,一般情況下,默認的框架是生成項目的.NET SDK的版本,我安裝的是,NET 5.0,那麼我又沒有更改<TargetFramework>,那麼運行dotnet build之後的輸出文件會在bin/Debug/net5.0之下。

  • -r|--runtime <RUNTIME_IDENTIFIER>

    指定目標運行時。 有關運行時標識符 (RID) 的列表,請參閱 RID 目錄如果將此選項與 .NET 6.0 SDK 結合使用,則還要使用 --self-contained--no-self-contained。指定了目標運行時之後,.NET 運行時隨應用程式一同發布。文件輸出在 bin/<configuration>/<target>/RID之中。常見的RID有win-x64win-x86Linux-x64

Debug vs Release

dotnet rundotnet builddotnet publish中都有一個選項,叫做-c|--configuration <CONFIGURATION>。微軟官方對這個選項的解釋是:

定義生成配置。 大多數項目的默認配置為 Debug,但你可以覆蓋項目中的生成配置設置。

顯然,微軟的官方解釋並不容易理解。其實Debug和Release兩種模式是.NET默認的兩種模式,默認情況下,Debug模式生成的pdb文件含有全部的斷點資訊,在進行調試的時候,保存著調試和項目狀態資訊、有斷言、堆棧檢查等程式碼。在Visual Studio裡面只有選擇Debug模式才會有完整的調試資訊,如果選擇Release模式進行調試,基本上只能輸出出什麼錯了+錯誤在哪行當然這只是.NET的默認設置,可以在Visual Studio裡面進行自定義的配置。在Visual Studio Code裡面,調試默認使用Debug模式。

Release模式並非一無是處,它編譯時對應用程式的速度進行優化,使得程式在程式碼大小和運行速度上都是最優的

下面提供一些互聯網上關於這兩個模式的討論:

The Release mode enables optimizations and generates without any debug data, so it is fully optimized. . Lots of your code could be completely removed or rewritten in Release mode. The resulting executable will most likely not match up with your written code. Because of this release mode will run faster than debug mode due to the optimizations.


來源:Difference between a Debug and Release build (net-informations.com)

The most important thing is that in Debug mode there are no optimizations, while in Release mode there are optimizations. This is important because the compiler is very advanced and can do some pretty tricky low-level improving of your code. As a result some lines of your code might get left without any instructions at all, or some might get all mixed up. Step-by-step debugging would be impossible. Also, local variables are often optimized in mysterious ways, so Watches and QuickWatches often don’t work because the variable is “optimized away”. And there are multitudes of other optimizations too. Try debugging optimized .NET code sometime and you’ll see.

Another key difference is that because of this the default Release settings don’t bother with generating extensive debug symbol information. That’s the .PDB file you might have noticed and it allows the debugger to figure out which assembly instructions corresspond to which line of code, etc.


來源:.net – What is the difference between Debug and Release in Visual Studio? – Stack Overflow

建議按照這兩個模式的字面意思來選擇,比如說在調試階段就用Debug模式,調試完畢之後的運行、構建和發布就用Release模式。這樣既能保證在調試時能夠看到最完整的調試資訊,也能保證程式碼運行、構建和發布速度更快。

項目結構

和運行dotnet run之後的項目結構一致,參見dotnet run項目結構

想要保證編譯後的文件能夠運行MyAppOne.dllMyAppOne.deps.jsonMyAppOne.runtimeconfig.json不可或缺的。如果覺得build之後生成的文件太多,可以嘗試使用.NET 6.0然後參考單文件應用程式 – .NET | Microsoft Docs,但是不建議在build階段去嘗試生成單文件,因為build的本質就是去生成項目及其所有依賴項。如果想要生成單文件,可以在部署階段(publish)來嘗試單文件部署。

dotnet publish

將應用程式及其依賴項發布到文件夾以部署到託管系統。

dotnet publish [<PROJECT>|<SOLUTION>] [-a|--arch <ARCHITECTURE>]
    [-c|--configuration <CONFIGURATION>]
    [-f|--framework <FRAMEWORK>] [--force] [--interactive]
    [--manifest <PATH_TO_MANIFEST_FILE>] [--no-build] [--no-dependencies]
    [--no-restore] [--nologo] [-o|--output <OUTPUT_DIRECTORY>]
    [--os <OS>] [-r|--runtime <RUNTIME_IDENTIFIER>]
    [--self-contained [true|false]]
    [--no-self-contained] [-v|--verbosity <LEVEL>]
    [--version-suffix <VERSION_SUFFIX>]

dotnet publish -h|--help

輸出文件默認位置是 bin/<configuration>/<target>/publish

常用命令選項

基本上和dotnet build 的常用命令選項一致,有個比較大的不同就是使用-r|--runtime <RUNTIME_IDENTIFIER>時可以指定self-contained,不需要像dotnet build一樣得等到.NET 6.0才能指定self-contained


來源:dotnet publish 命令 – .NET CLI | Microsoft Docs

項目結構

輸出文件默認位置是 bin/<configuration>/<target>/publish

默認狀態下項目結構和dotnet build項目結構不一樣的點有兩個,一個是沒有了ref文件夾,一個是沒有了runtimeconfig.dev.json

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2021/10/17      6:46            416 MyAppOne.deps.json
-a----        2021/10/15     16:01           4608 MyAppOne.dll
-a----        2021/10/15     16:01         125952 MyAppOne.exe
-a----        2021/10/15     16:01           9600 MyAppOne.pdb
-a----        2021/10/17      6:46            147 MyAppOne.runtimeconfig.json

單文件部署和可執行文件

單個文件部署與 Windows 7 不兼容。

有兩個好方法:

  1. 修改MSBuild(.csrpoj)文件,然後dotnet publish

    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net5.0</TargetFramework>
        <PublishSingleFile>true</PublishSingleFile>
        <SelfContained>true</SelfContained>
        <RuntimeIdentifier>win-x64</RuntimeIdentifier>
        <PublishReadyToRun>true</PublishReadyToRun>
      </PropertyGroup>
    
    </Project>
    
  2. 完全通過命令行,無需修改MSBuild(.csrpoj)文件

    dotnet publish -r linux-x64 -p:PublishSingleFile=true --self-contained true
    

原理解釋

首先,必須指定PublishSingleFile為真,這是生成單個文件的基礎。SelfContained為真保證可以獨立而不依賴框架,RuntimeIdentifier來指定系統和cpu類型,只有指定了這個,指定SelfContained才有意義

  • --self-contained [true|false]

    .NET 運行時隨應用程式一同發布,因此無需在目標電腦上安裝運行時。 如果指定了運行時標識符,並且項目是可執行項目(而不是庫項目),則默認值為 true。 有關詳細資訊,請參閱 .NET 應用程式發布使用 .NET CLI 發布 .NET 應用

    如果在未指定 truefalse 的情況下使用此選項,則默認值為 true。 在這種情況下,請不要緊接在 --self-contained 後放置解決方案或項目參數,因為該位置需要 truefalse


來源:dotnet publish 命令 – .NET CLI | Microsoft Docs

即使是發布單文件,--self-contained也要根據需求慎重考慮。

dotnet restore

dotnet restore 命令使用 NuGet 還原依賴項以及在 project 文件中指定的特定於項目的工具。 在大多數情況下,不需要顯式使用 dotnet restore 命令,因為在運行以下命令時,將會在必要時隱式運行 NuGet 還原:

build vs publish

最開始的時候,這倆的區別是輸出的文件能否在另一台機器上運行(is ready to be transferred to another machine to run.)。但是隨著.NET框架的發展,build和publish的含義也在發生著變化(其實這要怪微軟,不知道從什麼時候開始,感覺微軟整個公司氛圍開始變成說話不算數,朝令夕改了)。

本文無意去討論在整個漫長的.NET發展周期( .NET Framework.NET Core等 )內這兩個命令含義的變化,現在是2021年10月17日,目前最近的穩定版是.NET 5.0,所以本文只討論.NET 5.0中build和publish的區別。

  • 對於面向 .NET Core 3.0 及更高版本的可執行項目,庫依賴項會被複制到輸出文件夾。 這意味著如果沒有其他任何特定於發布的邏輯(例如,Web 項目具有的邏輯),則應可部署生成輸出。也就是說.NET 5.0在沒有特殊情況下,build和publish都做好了在另一台機器上的準備。
  • 相比較於運行dotnet build之後文件夾的變化,dotnet publish會在原有的基礎上生成bin/<configuration>/<target>/publish文件夾,publish文件夾和bin/<configuration>/<target>文件夾相比沒有了ref文件夾和runtimeconfig.dev.json文件。
  • dotnet publish適用於部署成單文件。
  • dotnet publish適用於定於生成的文件是否依賴框架(--self-contained [true|false])

MSBuild

.NET 應用是使用 MSBuild 從源程式碼中生成的。 項目文件(.csproj、.fsproj 或 .vbproj)指定目標和負責編譯、打包和發布程式碼的關聯任務 。 有引用目標和任務的標準集合的 SDK 標識符。 使用這些標識符有助於使項目文件較小且易於使用。

MSBuild進行修改有兩個方法,一個是在文件中修改,一個是在命令行中修改。其中在命令行中修改適用於dotnet build -pdotnet publish -p,但不適用於dotnet run。也可以用MSBuild.exe [Switches] [ProjectFile]來進行命令行修改,可以參考MSBuild 命令行參考 – MSBuild | Microsoft Docsdotnet publish 命令 – .NET CLI | Microsoft Docs

dotnet builddotnet publish可以用-p:<NAME>=<VALUE>來定義MSBuild屬性,常用的幾個屬性如下:

  • PublishSingleFile

    啟用單文件發布。 此外,還會在 dotnet build 期間啟用單一文件警告。

  • SelfContained

    確定應用是獨立的還是依賴於框架的。

  • RuntimeIdentifier

    指定目標 OS 和 CPU 類型。 默認情況下,還會設置 <SelfContained>true</SelfContained>

  • PublishReadyToRun

    啟用預先 (AOT) 編譯

注意,.NET 5.0中dotnet build設置SelfContained無效。

關於MSBuild的深度中文解析可以看理解 C# 項目 csproj 文件格式的本質和編譯流程 – walterlv

.NET CLI 發布 .NET 應用

.NET 提供了三種發布應用程式的方法。 依賴於框架的部署會生成一個跨平台 .dll 文件,此文件使用本地安裝的 .NET 運行時。 依賴於框架的可執行文件會生成一個特定於平台的可執行文件,此文件使用本地安裝的 .NET 運行時。 獨立式可執行文件會生成一個特定於平台的可執行文件,並包含 .NET 運行時的本地副本。

發布模式 SDK 版本 命令
依賴框架的部署 2.1 dotnet publish -c Release
3.1 dotnet publish -c Release -p:UseAppHost=false
5.0 dotnet publish -c Release -p:UseAppHost=false
依賴於框架的可執行文件 3.1 dotnet publish -c Release -r <RID> --self-contained false dotnet publish -c Release
5.0 dotnet publish -c Release -r <RID> --self-contained false dotnet publish -c Release
獨立部署 2.1 dotnet publish -c Release -r <RID> --self-contained true
3.1 dotnet publish -c Release -r <RID> --self-contained true
5.0 dotnet publish -c Release -r <RID> --self-contained true

上述表格來源:使用 .NET CLI 發布應用 – .NET | Microsoft Docs

另外,還可以通過PublishSingleFile來部署成單個可執行文件

參考文檔

LICENSE

copyright © 2021 蘇月晟,版權所有。

知識共享許可協議
作品蘇月晟採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。“

Tags: