在 Visual Studio 2019 新增 Class Library (類別程式庫)專案,現在有三個選項:

  • Class Library (.NET Standard)
  • Class Library (.NET Framework)
  • Class Library (.NET Core)

這篇簡單聊聊如何抉擇與實現類別程式庫跨平台。

開始前請確定你知道 .NET Standard 是什麼?簡單說它是針對 .NET 跨平台共用所制定的一套標準,確保依此標準寫出來的程式庫可以在 .NET Framework、.NET Core、Mono、UWP、Xamarin.iOS、Xamarin.Android (原本還有 Windows Phone,現在已經沒有了,鳴...) 等不同平台使用。

抓住這一點,如何選擇就很清楚了。如果你的開發環境只有 .NET Framework 及 .NET Core 兩種,程式庫想要兩邊共用,那 Class Library (.NET Standard) 是唯一選擇,否則,要給誰專用就選哪一種。

為什麼不一律都用 .NET Standard 就好?

.NET Standard 考量跨平台相容,限定只能使用各平台都能支援的 API,.NET Standard 版本從 1.0、1.1 到目前的 2.1 版,愈早的版本能支援的平台版本愈廣,相對的可用 API 也愈少。


表格來源: https://docs.microsoft.com/zh-tw/dotnet/standard/net-standard

若主要考量 .NET Core、.NET Framework 兩種平台,只能選 .NET Standard 2.0(含) 以下的版本,.NET Standard 2.1 可對映 .NET Core 3.0+,但 .NET Framework 無法支援,最高只能到 .NET Standard 2.0。(延伸閱讀:.NET Core 3.0 正式版! 告別 .NET Framework ,迎向 .NET 5)

.NET Standard 程式庫需依循某個特定數字版本,若考慮通吃 .NET Core 加 .NET Framework,.NET Standard 2.0 算是唯一解:

選 2.1 .NET Framework 不能用,選 2.0 以下的理論上也成,但愈低的版本能用的 API 愈少。別小看 API 數量差距,1.6 可用的 API 還不到 2.0 的一半 (13,501 vs 32,638),想拿慣用老虎鉗卻發現工具箱沒有,可以想像那感覺有多糟 XD:


來源:.NET Standard FAQ

如果你的程式邏輯單純,沒有綁不同平台專屬的動作,TargetFramework 選 .NET Standard 2.0,編譯成一顆 DLL 給所有平台用是最省事的方法。但老鳥都知道,代誌母系憨人所想 A 哈尼甘單,難免遇到不同平台做法有別的情境,例如:在 .NET Framework 讀 Registry,在 .NET Core 吃環境變數。此時需針對不同平台執行不同邏輯,.NET Standard 程式庫提供的做法是修改 .csproj,將 <TargetFramework>netstandard2.0</TargetFramework> 更名為 <TargetFrameworks> (加上s),並以分號分隔列舉 Target Framework Moniker (TFM),而引用程式庫時,可加上 Condition 指定不同 TargetFramework 參照不同程式庫。加入多個 TargetFramework 後 /bin/Debug 資料也會出現以 TFM 為名的資料夾:

以 TFM 為名的資料夾,內含該平台專用的 DLL 及相依組件庫:

程式部分則用 #if NET461、#if NETFRAMEWORK、#if NETSTANDARD2_0、#if NETSTANDARD、#else、#endif 等語法為不同平台撰寫專屬邏輯:參考 (註:程式是亂寫的,請不要問為什麼有這種鬼邏輯。)

using System;

namespace NetStdClassLib
{
    public class Foo
    {
        public static string Test()
        {
#if NET461
            return Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\.NETFramework")
                .GetValue("InstallRoot").ToString();
#else
            return Environment.GetEnvironmentVariable("TEMP");
#endif
        }
    }
}

使用多 TargetFramework 雖然不像標準 .NET Standard 程式庫用同一個 DLL 通吃各平台,但至少能做到單一專案維護多平台共用程式庫,但畢竟是次佳解,在系統開發時仍應優先考量使用 .NET Standard。

Tips of developing .NET Standard class library with Visual Studio.


Comments

# by cyy

這個有個問題在寫代碼的時候會按第一個來判斷語法是否合法,寫一些.net 4.7才有的語法糖就會提示不支持

# by 余小章

當我用 Net Standard 編譯多平台,專案設定如下 <PropertyGroup> <TargetFrameworks>net40;net45</TargetFrameworks> </PropertyGroup> 代碼裡面會有 #if NET40 ... #if NET45 ... #if NET 45 區段的代碼是淺色的,編譯時沒有 Intellisense,除錯時,不會執行; 當專案設定 <TargetFrameworks>net45;net40</TargetFrameworks> 順序交換,然後重開 VS,換 #if NET40 的代碼變淺色 XD

# by 余小章

或者,在 VS IDE 的編輯視窗左上角選擇 TargetFramework,這樣就不需要重新啟動 VS IDE

# by AS2001

此為新手小白提問,勿狂噴 環境 => 主要開發工具及框架:Visual Studio 2022、C# .Net Framework 4.8.0 (Winform) 作業系統:Windows 10 不知是否有方法,可讓 DLL 更新後, 讓其使用此 DLL 的專案自動吃到更新後的 DLL,不必再重新編譯一遍。 以上提問,感謝。

# by Jeffrey

to AS2001, 這種做法有好有壞,應用程式不經測試強迫改用新版 DLL 蠻容易出問題的,因此 .NET 主流做法是 Side-By-Side,每個程式決定自用的版本。若真的要採集中控管,可考慮將組件 DLL 註冊到 GAC,再配合 Publisher Policy 強制改用新版。 參考: 註冊 GAC https://learn.microsoft.com/zh-tw/dotnet/framework/app-domains/working-with-assemblies-and-the-gac?WT.mc_id=DOP-MVP-37580 發行者原則 https://learn.microsoft.com/zh-tw/previous-versions/visualstudio/visual-studio-2008/dz32563a(v=vs.90)?WT.mc_id=DOP-MVP-37580

# by AS2001

To Jeffrey: 感謝您的回覆, 因為目前有遇到一些使用情境,如資料庫連線物件等...通用內容, 而當物件當中需新增連線配置時,需重新編一版 DLL, 諸如此類的通用型 DLL,編譯完後,並不會想要有 "專案重編" 的動作,故才有此發問; 我相信對此案例,目前業界應有更適當的解決方案, 不知是否可提點一下? 感謝。

# by Jeffrey

to AS2001,依你的描述情境,偏向連線等系統設定面的異動,而非程式邏輯修改。業界較常用的處 理方式會將設定與程式分開,將設定保存在設定檔或 Registry 裡,把設定寫死在程式碼裡一般被視為不良設計,其中一項缺點便是你所遇到的,修改設定得重新編譯程式部署,工程浩大。 連線設定寫進程式另個可能問題此正式環境的帳號密碼會進入版控系統,被開發或維護人員知道,也增加被竊取或外流的風險。 我個人覺得比起設法克服經常要更新一堆 DLL 的挑戰,倒不如先回頭檢討是否真的非把連線配置包進 DLL 裡不可。以上淺見。

Post a comment