Write multiple 'Main' methods(.NET/C#), and switch to correct one.

Introduction

As all books or materials showed that a .NET/C# application starts with a Main method. But we can write multiple Main methods in our project, then we can choose the correct one you wanted.
You may think that multiple Main is useless, but when your project has different startup processes depend on different compile conditions or you have to change a lot of codes to start your application, it may be a good choice to write multiple Main methods.


Details

The original link is .NET/C# 中你可以在代码中写多个 Main 函数,然后按需要随时切换 - walterlv. But this blog was written in Chinese, so I translate its content to English. Waterlv is an MVP(Microsoft Most Valuable Professional), and he is good at .NET Core\WPF\.NET. Here is his Blog.

Where can we choose the correct Main method.

At the project which has Main method, the steps are listed below :
Right click (show menu items)->Properties->Application->Startup object . As we can see,the default value of startup object is Not set.

Choose the correct Main method.

If there are multiple Main methods, and we don’t select a value at Startup object (the value is “Not set”), we will get an error when we compile these codes.

Here is the message of the compile error.

Error CS0017
The 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

We can fix this error by choosing the correct Main method.

We write a WPF application

At this step, we can do some more complicated works that we changed the application to the WPF application.

Change the startup object to Walterlv.Demo.App.

Then, we start this WPF application.

It is important to note that, if you use new .csproj file in your project, the contents are as follows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<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>

We keep the default code in App.xaml file.

1
2
3
4
<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>

And the codes in App.xaml.cs file are pretty simple.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}
}
}

When we run the application now, the Main methods in Program file and NewProgram file do not work, because we set Walterlv.Demo.App as the value of Startup Object.

Switch different startup processes depending on the Startup Object

Now, you got a functional requirement

The startup processes changes depending on the value of Startup Object.

Specifically, if we start the Main method in Program file we startup an APP, also if we start Main in NewProgram we run another APP.
So, we can create a new file APP.new.xaml.cs which share the same xaml file (APP.xaml) with App.xaml.cs.
And we have to change the code in .csproj, the codes are as follow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<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.NewProgram</StartupObject>
</PropertyGroup>
<!-- region: added code-->
<PropertyGroup Condition=" '$(StartupObject)' == 'Walterlv.Demo.Program' ">
<!-- run app.xaml.cs -->
<AppCsPath>App.xaml.cs</AppCsPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(StartupObject)' == 'Walterlv.Demo.NewProgram' ">
<!-- run app.new.xaml.cs -->
<AppCsPath>App.new.xaml.cs</AppCsPath>
</PropertyGroup>
<!-- endregion -->
<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)" />

<!--region: added code-->
<!-- remove all APP file,because they will be added later -->
<Compile Remove="App.xaml.cs" />
<Compile Remove="App.new.xaml.cs" />
<Compile Include="$(AppCsPath)" DependentUpon="App.xaml" SubType="Designer" />
<!--endregion-->
</ItemGroup>

</Project>

We can se that it add some judgement code in this .csproj, it can run deffirent .xaml.cs files depend on the value of $(StartupObject).
Base on the above, we have different .xaml.cs file for different values of Startup Object. We can write different codes in these two files.
For example,the code in App.new.xaml.cs are as followed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
}
}
}

They are different with the codes in App.xaml.cs,we change the title of window in these code.

Replace files with conditional compilers

If there are just a little differences among your startup processes in your project, you can use the conditional compiler to replace files.

1
2
3
4
5
6
<PropertyGroup Condition=" '$(StartupObject)' == 'Walterlv.Demo.Program' ">
<DefineConstants>$(DefineConstants);OLD</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(StartupObject)' == 'Walterlv.Demo.NewProgram' ">
<DefineConstants>$(DefineConstants);NEW</DefineConstants>
</PropertyGroup>

Currently,you can use coditional compilers to control your startup processes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
}
}
}