1. 程式人生 > >.NET/C# 中你可以在程式碼中寫多個 Main 函式,然後按需要隨時切換

.NET/C# 中你可以在程式碼中寫多個 Main 函式,然後按需要隨時切換

.NET/C# 程式從 Main 函式開始執行,基本上各種書籍資料都是這麼寫的。不過,我們可以寫多個 Main 函式,然後在專案檔案中設定應該選擇哪一個 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 函式 ▲ 選擇一個 Main 函式

我們準備一個 WPF 程式

現在,我們來一些更復雜的操作。現在把我們的專案換成一個普通的 WPF 專案。

普通 WPF 專案 ▲ 普通 WPF 專案

把啟動物件換成 Walterlv.Demo.App:

更換啟動物件為

於是,我們可以啟動我們的 WPF 專案。

新啟動的 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);
            }
        }
    }