.NET/C# 中你可以在程式碼中寫多個 Main 函式,然後按需要隨時切換
.NET/C# 程式從 Main 函式開始執行,基本上各種書籍資料都是這麼寫的。不過,我們可以寫多個 Main 函式,然後在專案檔案中設定應該選擇哪一個 Main 函式。
你可能會覺得這樣沒有什麼用,不過如果你的應用程式在不同的編譯條件下有不同的啟動程式碼,或者你需要持續去大範圍修改啟動程式碼,那麼做一個 Main 函式的選擇器是一個不錯的選擇。
在哪裡選擇 Main?
在帶有 Main 函式的專案上 “右鍵 -> 屬性 -> 應用 -> 啟動物件”,可以看到我們的 Main 函式,預設值是 “未設定”。
▲ 選擇 Main 函式
在我們保持這個值沒有設定的情況下,如果寫兩個 Main 函式,那麼就會出現編譯錯誤。
Error CS0017 Program has more than one entry point defined. Compile with /main to specify the type that contains the entry point. Walterlv.Demo.Main C:\Users\lvyi\Desktop\Walterlv.Demo.Main\Walterlv.Demo.Main\NewProgram.cs
這時,從兩個 Main 函式中選擇一個就好了。
▲ 選擇一個 Main 函式
我們準備一個 WPF 程式
現在,我們來一些更復雜的操作。現在把我們的專案換成一個普通的 WPF 專案。
▲ 普通 WPF 專案
把啟動物件換成 Walterlv.Demo.App:
於是,我們可以啟動我們的 WPF 專案。
▲ 新啟動的 WPF 程式
這是個 Demo 程式,程式碼比較簡單。值得注意的是,如果使用新的 csproj 檔案,其內容如下:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net472</TargetFramework> <LanguageTargets>$(MSBuildToolsPath)\Microsoft.CSharp.targets</LanguageTargets> <RootNamespace>Walterlv.Demo</RootNamespace> <StartupObject>Walterlv.Demo.App</StartupObject> </PropertyGroup> <ItemGroup> <Reference Include="PresentationCore" /> <Reference Include="PresentationFramework" /> <Reference Include="System.Xaml" /> <Reference Include="WindowsBase" /> </ItemGroup> <ItemGroup> <ApplicationDefinition Include="App.xaml" SubType="Designer" Generator="MSBuild:Compile" /> <Page Include="**\*.xaml" Exclude="App.xaml" SubType="Designer" Generator="MSBuild:Compile" /> <Compile Update="**\*.xaml.cs" DependentUpon="%(Filename)" /> </ItemGroup> </Project>
你可以通過閱讀 ofollow,noindex" target="_blank">將 WPF、UWP 以及其他各種型別的舊 csproj 遷移成基於 Microsoft.NET.Sdk 的新 csproj 完成這樣的新舊格式遷移。
App.xaml 中保持預設的程式碼即可:
<Application x:Class="Walterlv.Demo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> </Application>
App.xaml.cs 中的程式碼比較簡單,就是啟動一個 MainWindow:
using System.Windows; namespace Walterlv.Demo { public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { var window = new MainWindow(); window.Show(); base.OnStartup(e); } } }
這時,我們的 Program 和 NewProgram 還是保持之前的程式碼不變,因為我們的啟動物件已經被設定為了 Walterlv.Demo.App,所以這裡的兩個 Main 函式其實並沒有起作用。
根據啟動物件的不同,控制不同的啟動流程
現在,我們即將實現一個功能:
- 當在屬性頁中切換啟動物件的時候,我們的啟動流也能跟著改變。
具體來說,我們的 Program 啟動一個 App,而 NewProgram 啟動另一個 App。
於是,我們在 App.xaml.cs 之外再新建一個 App.new.xaml.cs。這兩個 App 類可以共用一個 App.xaml 檔案。
於是我們需要修改 csproj 的程式碼(以下紅色表示刪除的行,綠色表示新增的行):
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net472</TargetFramework> <LanguageTargets>$(MSBuildToolsPath)\Microsoft.CSharp.targets</LanguageTargets> <RootNamespace>Walterlv.Demo</RootNamespace> -<StartupObject>Walterlv.Demo.App</StartupObject> +<StartupObject>Walterlv.Demo.NewProgram</StartupObject> </PropertyGroup> +<PropertyGroup Condition=" '$(StartupObject)' == 'Walterlv.Demo.Program' "> +<!-- 啟用原啟動流中的 App.xaml.cs 檔案 --> +<AppCsPath>App.xaml.cs</AppCsPath> +</PropertyGroup> +<PropertyGroup Condition=" '$(StartupObject)' == 'Walterlv.Demo.NewProgram' "> +<!-- 啟用新啟動流中的 App.xaml.cs 檔案 --> +<AppCsPath>App.new.xaml.cs</AppCsPath> +</PropertyGroup> + <ItemGroup> <Reference Include="PresentationCore" /> <Reference Include="PresentationFramework" /> <Reference Include="System.Xaml" /> <Reference Include="WindowsBase" /> </ItemGroup> <ItemGroup> <ApplicationDefinition Include="App.xaml" SubType="Designer" Generator="MSBuild:Compile" /> <Page Include="**\*.xaml" Exclude="App.xaml" SubType="Designer" Generator="MSBuild:Compile" /> <Compile Update="**\*.xaml.cs" DependentUpon="%(Filename)" /> +<!-- 刪掉兩個 App.xaml.cs 檔案,以便後面可以重新新增 --> +<Compile Remove="App.xaml.cs" /> +<Compile Remove="App.new.xaml.cs" /> +<Compile Include="$(AppCsPath)" DependentUpon="App.xaml" SubType="Designer" /> </ItemGroup> </Project>
增加的判斷其實是根據 $(StartupObject)
值的不同,設定不同的 App.xaml.cs 檔案與 App.xaml 檔案對應。於是,我們也可以有不同的 App.xaml.cs 檔案了。
比如我們的 App.new.xaml.cs 檔案中的內容就與 App.xaml.cs 中的不一樣。
using System.Windows; namespace Walterlv.Demo { public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { var window = new MainWindow { Title = "New Walterlv Demo", }; window.Show(); base.OnStartup(e); } } }
在新的檔案中,我們修改了視窗的標題。
▲ 新設定的視窗標題
通過切換啟動物件,我們的解決方案窗格中也能顯示不同的 App.xaml.cs 檔案。(不過需要提醒,可能需要解除安裝然後重新載入專案才會看到修改;否則只是能夠編譯通過,但看不見檔案。)
▲ 可以看得見兩個檔案的切換
由於 window
是區域性變數,所以 Main
函式中是不能修改到的。而採用了這種根據啟動物件不同動態改變 App.xaml.cs 的方式解決了這個問題。
將不同的檔案換成不同的條件編譯符
如果你的啟動流程差異並不是那麼大,那麼也可以使用條件編譯符的定義來替代整個檔案的替換。
<PropertyGroup Condition=" '$(StartupObject)' == 'Walterlv.Demo.Program' "> -<AppCsPath>App.xaml.cs</AppCsPath> +<DefineConstants>$(DefineConstants);OLD</DefineConstants> </PropertyGroup> <PropertyGroup Condition=" '$(StartupObject)' == 'Walterlv.Demo.NewProgram' "> -<AppCsPath>App.new.xaml.cs</AppCsPath> +<DefineConstants>$(DefineConstants);NEW</DefineConstants> </PropertyGroup>
這時,可以通過條件編譯符來控制新舊啟動程式碼:
using System.Windows; namespace Walterlv.Demo { public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { var window = new MainWindow() +#if NEW { Title = "New Walterlv Demo", }; +#endif window.Show(); base.OnStartup(e); } } }
本文會經常更新,請閱讀原文: https://walterlv.com/post/write-multiple-main-and-related-startup-codes.html ,以避免陳舊錯誤知識的誤導,同時有更好的閱讀體驗。
本作品採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名 呂毅 (包含連結:https://walterlv.com ),不得用於商業目的,基於本文修改後的作品務必以相同的許可釋出。如有任何疑問,請 與我聯絡 ([email protected]) 。