.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>
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);
}
}
}