現在的 .NET 開發環境已經是百花齊放,除了可以在 Windows 平台下開發出可在 .NET Framework 的應用程式,還可以在 Linux、macOS、UWP、Android、iOS 等不同作業系統下,使用 .NET 技術進行應用程式的開發。
不過,若要能夠讓我們開發出來的類別可以在不同專案或者不同平台下能夠做到共用與分享,該使用甚麼樣的方法呢?在本篇文章中,我們將會進行不同角度的探討與測試。
只有 Windows 作業系統環境下
在以往的 .NET 生態系統中,若想要開發出 .NET 應用程式,此時,就只能選擇在 Windows 作業系統下來執行這個應用程式,並且,所使用的 .NET 運行生態,也就是我們常在使用的 .NET Framework 透過 .NET Framework 的開發環境,我們可以使用 BCL (基底類別庫 Base Class Library) / FCL (框架類別庫 Framework Class Library) 所提供豐富的類別,進行開發出 Windows Forms / WPF / ASP.NET Web Forms / ASP.NET MVC 這樣不同類型的應用程式。
當然,這段時期,也有使用 .NET Framework 子集合環境所創造出來的,如:Silverlight、Windows Phone 7.x、.NET Compact等等,不過,由於這些環境已經不再支援,所以,我們將不會進行討論
當然,這段時期,也有使用 .NET Framework 子集合環境所創造出來的,如:Silverlight、Windows Phone 7.x、.NET Compact等等,不過,由於這些環境已經不再支援,所以,我們將不會進行討論
在專案之間共用類別庫
在 .NET Framework 開發環境下,我們可以設計一個屬於自己環境使用的類別庫 Class Library (這裡會加入我們自行開發的不同類別、介面、委派等型別與不同商業邏輯 API) 專案,經過建置之後,就會成為一個類別庫組件 Class Library Assembly;之後,我們可以讓不同的 .NET Framework 專案,將這個類別庫專案的參考,加入到正在開發的專案內,如此,便可以共享與共用這個類別庫裡面所設計出來的各種商業邏輯與類別。
由於我們透過類別庫進行設計出自己或者公司需要用到的各種不同類別與功能和計算能力,全部整合在一個或者多個組件 Assembly 內,一旦這些商業邏輯或者類別有修正的時候,我們就僅僅需要維護一套類別庫專案原始碼即可,就可以將修正後的類別庫組件讓其他專案來更新與使用。這就是 .NET 類別庫的主要目的,可以讓您共享自行開發的 .NET 類別於不同專案上來使用。
.NET 可以跨平台了 Windows / Linux / macOS / Android / iOS / UWP
現在,在 .NET 生態系統中,除了可以在 Windows 作業系統下執行,也可以在 Linux / macOS / Android / iOS / UWP 系統下開發出可以執行 .NET 應用程式。因此,在 .NET 生態系統中,除了以往的 .NET Framework 之外,另外增加了 .NET Core 與 Xamarin 這兩個開發生態環境。
在這三個 .NET 開發生態環境中,.NET Framework / .NET Core / Xamarin,他們都各自有屬於自己定義出來的 BCL (基底類別庫 Base Class Library) 與通用語言執行階段 CLR (Common Language Runtime) ,最重要的是,這些 BCL 並不是在每個平台下都有完整的設計與實作出來(最完整的 .NET 基底類別庫,則是完全實作在 .NET Framework生態環境中)。
現在,我們遇到一個問題,當我們需要設計出屬於自己會重複使用到的類別庫,要共享這些程式碼到不同平台、不同專案下,該如何進行設計呢?
在每個 .NET 平台建立屬於該平台下的類別庫
我們可以在這三個 .NET 平台 (.NET Framework, .NET Core, Mono Xamarin)中,分別建立出這三個平台會用到的類別庫專案;如此,每個.NET平台下的不同專案,就可以參考該平台所建立的類別庫專案,這樣,就可以彼此互相共享類別與商業邏輯設計了。
使用這樣解決方案,你需要維護可以分別在 .NET Framework / .NET Core / Mono Xamarin 平台下的三套類別庫原始碼。
在這篇文章,將會透過 Visual Studio 2017 所建立一個方案,進行相關問題測試;在這個方案內,我們建立了許多類別庫與各平台的可執行專案,如下圖所示。
在這個測試方案中,我們分別建立了
-
.NET Framework 類別庫專案
-
.NET Core 類別庫專案
-
Android 類別庫專案
-
PCL 可攜式專案 (這裡建立的是 Profile259 的可攜式專案類別庫)
-
.NET 標準類別庫專案 ( .NET Standard Class Library)
想要建立這些不同平台的類別庫,可以在 Visual Studio 2017 內,增加一個專案項目,當 新增專案
對話窗出現之後,你可以在右上方的搜尋條件文字輸入盒內,輸入 類別庫 C#
關鍵字,此時,對話窗終將會列出可以新建的類別庫專案類型,如下圖所示。
你也許會想到說,幹嘛這麼麻煩,這很簡單,我們只需要建立一個 .NET Framework 的類別庫,並且將它的參考加入到 .NET Core / Xamarin 專案內,這樣,我們就可以透過 .NET Framework 所創建出來的類別庫,達到共享程式碼的目的了,而且我們也只需要維護一套類別庫原始碼。
所以,有時候,現實是殘酷的,這樣的想法與做法,還是無法解決我們跨平台專案共享程式碼的需求,在底下的文章內容,你將會看到這樣開發過程所產生的問題。
使用這樣解決方案,你需要維護可以分別在 .NET Framework / .NET Core / Mono Xamarin 平台下的三套類別庫原始碼。
.NET Framework 類別庫專案
.NET Core 類別庫專案
Android 類別庫專案
PCL 可攜式專案 (這裡建立的是 Profile259 的可攜式專案類別庫)
.NET 標準類別庫專案 ( .NET Standard Class Library)
新增專案
對話窗出現之後,你可以在右上方的搜尋條件文字輸入盒內,輸入 類別庫 C#
關鍵字,此時,對話窗終將會列出可以新建的類別庫專案類型,如下圖所示。建立每個平台下的可執行專案
為了要能夠讓我們可以在不同的 .NET 平台下可以執行這些平台的應用程式,所以,我們接著透過 Visual Studio 2017 建立起不同平台的可執行專案:
-
NETFrameworkConsoleApp
這是可以在 Windows 作業系統下的主控台應用程式
-
NETCoreConsoleApp
這是可以在 Windows / Linux / macOS 作業系統下的主控台應用程式
-
AndroidApp
這是可以在 Android 裝置下執行的行動應用程式 (Mobile App)
要建立上述不同 .NET 平台下的可執行專案,請在如下圖的 新增專案
對話窗中,選擇適當的專案類型即可。
NETFrameworkConsoleApp
這是可以在 Windows 作業系統下的主控台應用程式
NETCoreConsoleApp
這是可以在 Windows / Linux / macOS 作業系統下的主控台應用程式
AndroidApp
這是可以在 Android 裝置下執行的行動應用程式 (Mobile App)
新增專案
對話窗中,選擇適當的專案類型即可。設計 .NET Framework 類別庫,使其在 .NET Framework 下共用
在這裡,我們在 .NET Framework 的 NETFrameworkClassLibrary
類別庫專案內,設計一個類別,如下所示:
這個類別庫 (Class Library)專案, FrameworkClass
, 設計的相當簡單,他只有一個屬性成員,並且有建立這個屬性的物件。這裡用到的類別 Bitmap
將會是由 .NET Framework 中的基底類別庫 (BCL) 中所提供的。
public class FrameworkClass
{
public Bitmap MyProperty { get; set; } = new Bitmap(100, 100);
}
接著,我們需要在 NETFrameworkConsoleApp
可執行專案下,加入 .NET Framework 類別庫的專案 NETFrameworkClassLibrary
,如此,我們才可以在這個可執行專案內,使用這個 .NET Framework 類別庫內的自行開發設計的型別。
請在 Visual Studio 2017 內,在 NETFrameworkConsoleApp
專案加入參考
請參考下圖,加入 NETFrameworkClassLibrary
類別庫專案到這個可執行專案內
由於,這個類別庫會參考用到了 BCL 的 System.Drawing 這個組件,所以,請在同樣的 參考管理員
對話窗內,點選 組件
頁次,接著勾選 System.Drawing 項目。
好的,現在我們可以到 NETFrameworkConsoleApp
專案內,使用 NETFrameworkClassLibrary
類別庫內的 FrameworkClass
類別了。
我們在可執行專案內的 Program.cs檔案內之方法 Main 內,撰寫了底下程式碼,並且請您執行該專案;此時,您將不會得到任何例外異常錯誤,這個專案可以正常執行。
class Program
{
static void Main(string[] args)
{
var fooObject = new NETFrameworkClassLibrary.FrameworkClass();
var barObject = fooObject.MyProperty;
Console.WriteLine("Hello World!");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
}
}
NETFrameworkClassLibrary
類別庫專案內,設計一個類別,如下所示:FrameworkClass
, 設計的相當簡單,他只有一個屬性成員,並且有建立這個屬性的物件。這裡用到的類別 Bitmap
將會是由 .NET Framework 中的基底類別庫 (BCL) 中所提供的。 public class FrameworkClass
{
public Bitmap MyProperty { get; set; } = new Bitmap(100, 100);
}
NETFrameworkConsoleApp
可執行專案下,加入 .NET Framework 類別庫的專案 NETFrameworkClassLibrary
,如此,我們才可以在這個可執行專案內,使用這個 .NET Framework 類別庫內的自行開發設計的型別。NETFrameworkConsoleApp
專案加入參考NETFrameworkClassLibrary
類別庫專案到這個可執行專案內參考管理員
對話窗內,點選 組件
頁次,接著勾選 System.Drawing 項目。NETFrameworkConsoleApp
專案內,使用 NETFrameworkClassLibrary
類別庫內的 FrameworkClass
類別了。 class Program
{
static void Main(string[] args)
{
var fooObject = new NETFrameworkClassLibrary.FrameworkClass();
var barObject = fooObject.MyProperty;
Console.WriteLine("Hello World!");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
}
}
共享 .NET Framework 類別庫給 .NET Core 的可執行專案
類別庫存在的目的,就是要能夠重複使用(避免重複設計,這樣也會避免因為重複的程式碼,造成更多出錯的機會)這些類別,因此,我們來將 .NET Framework 類別庫加入到 .NET Core 的專案內。
我們將 NETCoreConsoleApp
專案內,打開 參考管理員 - NETCoreConsoleApp
對話窗,找到 專案 頁次,勾選 NETFrameworkClassLibrary
項目,將這個專案參考加入到 NETCoreConsoleApp
專案內。
另外,所們在這裡 參考管理員 - NETCoreConsoleApp
似乎沒有看到如同 .NET Framework 平台下的 組件 頁次,可以讓我們選擇 System.Drawing 這個組件
在您加入完這類別庫參考之後,請重新開啟 參考管理員 - NETCoreConsoleApp
,很不幸的,所們在這裡 參考管理員 - NETCoreConsoleApp
似乎沒有看到如同 .NET Framework 平台下的 組件 頁次,可以讓我們選擇 System.Drawing 這個組件。
在 .NET Core 中,現在還沒有支援這個 BCL 類別 Bitmap
,所以,就算你可以正常把 .NET Framework 類別庫加入到 .NET Core 的可執行專案內,在 .NET Core 的專案內,還是無法正常建置與執行。
我們在 NETCoreConsoleApp
撰寫同樣的程式碼
class Program
{
static void Main(string[] args)
{
var fooObject = new NETFrameworkClassLibrary.FrameworkClass();
var barObject = fooObject.MyProperty;
Console.WriteLine("Hello World!");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
}
}
您會看到底下的錯誤訊息
錯誤 CS0012 類型 'Bitmap' 定義在未參考的組件中。您必須加入組件 'System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' 的參考。 NETCoreConsoleApp D:\Temp\NETClassLibrary\NETCoreConsoleApp\Program.cs 12 使用中
不過,若我們將 var barObject = fooObject.MyProperty;
這行程式碼註解起來,您將會發現到,這個 .NET Core 專案竟然可以建置成功了。
不過,不要高興得太早,我們將這個 .NET Core 可執行專案設定為預設起始專案,並且執行這個專案,您將會得到底下的執行時期錯誤訊息。
發生 System.IO.FileNotFoundException
HResult=0x80070002
Message=Could not load file or assembly 'System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. 系統找不到指定的檔案。
Source=<無法評估例外狀況來源>
StackTrace:
at NETFrameworkClassLibrary.FrameworkClass..ctor() in D:\Temp\NETClassLibrary\NETFrameworkClassLibrary\FrameworkClass.cs:line 12
at NETCoreConsoleApp.Program.Main(String[] args) in D:\Vulcan\GitHub\CSharpOOP\NETClassLibrary\NETCoreConsoleApp\Program.cs:line 11
class Program
{
static void Main(string[] args)
{
var fooObject = new NETFrameworkClassLibrary.FrameworkClass();
//var barObject = fooObject.MyProperty;
Console.WriteLine("Hello World!");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
}
}
所以,若我們想要設計出一個可以用於不同 .NET 平台都可以執行的類別庫專案,該如何解決此一問題呢?
NETCoreConsoleApp
專案內,打開 參考管理員 - NETCoreConsoleApp
對話窗,找到 專案 頁次,勾選 NETFrameworkClassLibrary
項目,將這個專案參考加入到 NETCoreConsoleApp
專案內。參考管理員 - NETCoreConsoleApp
似乎沒有看到如同 .NET Framework 平台下的 組件 頁次,可以讓我們選擇 System.Drawing 這個組件參考管理員 - NETCoreConsoleApp
,很不幸的,所們在這裡 參考管理員 - NETCoreConsoleApp
似乎沒有看到如同 .NET Framework 平台下的 組件 頁次,可以讓我們選擇 System.Drawing 這個組件。Bitmap
,所以,就算你可以正常把 .NET Framework 類別庫加入到 .NET Core 的可執行專案內,在 .NET Core 的專案內,還是無法正常建置與執行。NETCoreConsoleApp
撰寫同樣的程式碼 class Program
{
static void Main(string[] args)
{
var fooObject = new NETFrameworkClassLibrary.FrameworkClass();
var barObject = fooObject.MyProperty;
Console.WriteLine("Hello World!");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
}
}
錯誤 CS0012 類型 'Bitmap' 定義在未參考的組件中。您必須加入組件 'System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' 的參考。 NETCoreConsoleApp D:\Temp\NETClassLibrary\NETCoreConsoleApp\Program.cs 12 使用中
var barObject = fooObject.MyProperty;
這行程式碼註解起來,您將會發現到,這個 .NET Core 專案竟然可以建置成功了。發生 System.IO.FileNotFoundException
HResult=0x80070002
Message=Could not load file or assembly 'System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. 系統找不到指定的檔案。
Source=<無法評估例外狀況來源>
StackTrace:
at NETFrameworkClassLibrary.FrameworkClass..ctor() in D:\Temp\NETClassLibrary\NETFrameworkClassLibrary\FrameworkClass.cs:line 12
at NETCoreConsoleApp.Program.Main(String[] args) in D:\Vulcan\GitHub\CSharpOOP\NETClassLibrary\NETCoreConsoleApp\Program.cs:line 11
class Program
{
static void Main(string[] args)
{
var fooObject = new NETFrameworkClassLibrary.FrameworkClass();
//var barObject = fooObject.MyProperty;
Console.WriteLine("Hello World!");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
}
}
想要在 .NET 跨平台開發環境下,建立各平台都可以用的類別庫
從剛剛的過程中,我們發現到,我們可以在各個平台 .NET Framework
/ .NET Core
/ Xamarin
下,建立自己平台的類別庫,這些類別庫,是可以在自己的平台下重複使用(只要加入這個類別庫到自己的專案內即可);可是,我們卻無法跨平台的將 A 平台的類別庫,加入到 B 平台專案內,例如上面個過程,將 .NET Framework 建立的類別庫,加入到 .NET Core 專案內來使用。
對於這樣跨平台的類別庫需求,.NET 生態系統提供了兩種選擇 可攜式類別庫 (PCL Portable Class Library
與 .NET 標準類別庫 (.NET Standard Class Library)
。
.NET Framework
/ .NET Core
/ Xamarin
下,建立自己平台的類別庫,這些類別庫,是可以在自己的平台下重複使用(只要加入這個類別庫到自己的專案內即可);可是,我們卻無法跨平台的將 A 平台的類別庫,加入到 B 平台專案內,例如上面個過程,將 .NET Framework 建立的類別庫,加入到 .NET Core 專案內來使用。可攜式類別庫 (PCL Portable Class Library
與 .NET 標準類別庫 (.NET Standard Class Library)
。第一代的跨平台類別庫 : PCL 可攜式類別庫
PCL 可攜式類別庫是早期用於 .NET 跨平台類別庫的共享技術,在您建立一個 PCL 可攜式類別庫 的時候,您需要指定這個 PCL 可攜式類別庫需要與那些平台共享,一旦決定可以共享的平台之後,在這個 PCL 可攜式類別庫 內,可以使用的類別,就是這些平台都有提供的類別與成員和方法。
我們在這裡建立一個 Profile259
的 PCL 可攜式類別庫,可是,當我們要建立一個類別,裡面會有個 HttpClient
成員。
public class PCL259Class
{
HttpClient client;
}
您會看到底下錯誤訊息,竟然無法使用 HttpClient 這個類別,那麼,我們如同 .NET Framework 平台下,將這個 HttpClient 類別的組件加入進來,不就解決掉這個問題了嗎?不幸的是,當您打開了 參考管理員 - PCL259ClassLibrary
對話窗,切換到 組件 頁次,且是得到此訊息 所有 Framework 組件都已經被參考了,請使用物件瀏覽器瀏覽 Framework 中的參考
。這是因為在 PCL 可攜式類別庫 內,採用的技術是將要支援的不同平台的已經存在的類別庫,以交集做運算,僅提供每個平台都有提供的類別,才能夠在 PCL 可攜式類別庫 內使用。
錯誤 CS0246 找不到類型或命名空間名稱 'HttpClient' (是否遺漏了 using 指示詞或組件參考?) PCL259ClassLibrary D:\Temp\NETClassLibrary\PCL259ClassLibrary\PCL259Class.cs 11 使用中
Profile259
的 PCL 可攜式類別庫,可是,當我們要建立一個類別,裡面會有個 HttpClient
成員。 public class PCL259Class
{
HttpClient client;
}
參考管理員 - PCL259ClassLibrary
對話窗,切換到 組件 頁次,且是得到此訊息 所有 Framework 組件都已經被參考了,請使用物件瀏覽器瀏覽 Framework 中的參考
。這是因為在 PCL 可攜式類別庫 內,採用的技術是將要支援的不同平台的已經存在的類別庫,以交集做運算,僅提供每個平台都有提供的類別,才能夠在 PCL 可攜式類別庫 內使用。錯誤 CS0246 找不到類型或命名空間名稱 'HttpClient' (是否遺漏了 using 指示詞或組件參考?) PCL259ClassLibrary D:\Temp\NETClassLibrary\PCL259ClassLibrary\PCL259Class.cs 11 使用中
下一代的跨平台類別庫 : .NET 標準類別庫 (.NET Standard Class Library)
這是微軟針對 PCL 可攜式類別庫 所面臨到的困境,設計出來的跨平台類別庫設計方法。
他與 PCL 可攜式類別庫的不同在於:
-
PCL 可攜式類別庫
是針對現有平台可以支援的 BCL 所有類別,與要支援平台,定義出一個
Profile#
,例如,上面的例子中,我們建立一個 Profile259 的PCL 可攜式類別庫;在這個 Profile259 的 PCL 可攜式類別庫可以使用的類別,將會取其各平台 BCL 都有的類別,取其交集,這樣,就可以在 PCL 可攜式類別庫 使用這些類別。
-
.NET 標準類別庫 ( .NET Standard )
.NET 標準類別庫的作法卻與PCL 可攜式類別庫完全不同。.NET 標準類別庫是一個規格,在.NET 標準類別庫訂出的一個新版本規格之後,所有平台 BCL 都需要能夠支援這個 .NET 標準類別庫的新標準,因此,只要這個新版本的.NET 標準類別庫有列出的類別,您就可以在 .NET 標準類別庫 內使用這些類別與型別。
如此,當您設計好一個屬於自己的 .NET 標準類別庫後,就可以在不同平台下的專案,把這個 .NET 標準類別庫直接加入其參考,並且可以正常與順利使用與執行這些共用類別與相關 API。
PCL 可攜式類別庫
是針對現有平台可以支援的 BCL 所有類別,與要支援平台,定義出一個
Profile#
,例如,上面的例子中,我們建立一個 Profile259 的PCL 可攜式類別庫;在這個 Profile259 的 PCL 可攜式類別庫可以使用的類別,將會取其各平台 BCL 都有的類別,取其交集,這樣,就可以在 PCL 可攜式類別庫 使用這些類別。
.NET 標準類別庫 ( .NET Standard )
.NET 標準類別庫的作法卻與PCL 可攜式類別庫完全不同。.NET 標準類別庫是一個規格,在.NET 標準類別庫訂出的一個新版本規格之後,所有平台 BCL 都需要能夠支援這個 .NET 標準類別庫的新標準,因此,只要這個新版本的.NET 標準類別庫有列出的類別,您就可以在 .NET 標準類別庫 內使用這些類別與型別。
如此,當您設計好一個屬於自己的 .NET 標準類別庫後,就可以在不同平台下的專案,把這個 .NET 標準類別庫直接加入其參考,並且可以正常與順利使用與執行這些共用類別與相關 API。
建立一個各平台都可以用的類別庫
我們在這裡建立一個.NET 標準類別庫,NETStandardClassLibrary,並且將這個專案參考,加入到各平台專案內;若要建立一個.NET 標準類別庫,請在新增專案的時候,選擇如下圖的 .NET Standard
標籤頁次,接著選取 類別庫 (.NET Standard)
項目即可。
接著,我們在這個 NETStandardClassLibrary 專案內,設計底下 StandardClass,它會抓取微軟網站的內容,並且回傳這個網站內容字串。
public static class StandardClass
{
public static async Task<string> GetContent()
{
HttpClient client = new HttpClient();
var result = await client.GetStringAsync("http://www.microsoft.com");
return result;
}
}
接著,我們分別在 .NET Framework / .NET Core 平台下的專案,使用底下程式碼來進行測試,結果是可以正常運作的。
static void Main(string[] args)
{
var content = NETStandardClassLibrary.StandardClass.GetContent().Result;
Console.WriteLine($"Web Content Length : {content.Length}");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
}
.NET Standard
標籤頁次,接著選取 類別庫 (.NET Standard)
項目即可。 public static class StandardClass
{
public static async Task<string> GetContent()
{
HttpClient client = new HttpClient();
var result = await client.GetStringAsync("http://www.microsoft.com");
return result;
}
}
static void Main(string[] args)
{
var content = NETStandardClassLibrary.StandardClass.GetContent().Result;
Console.WriteLine($"Web Content Length : {content.Length}");
Console.WriteLine("Press any key for continuing...");
Console.ReadKey();
}