当前位置: 首页 > news >正文

.NET/C# 中你可以在代码中写多个 Main 函数,然后按需要随时切换

.NET/C# 程序从 Main 函数开始执行,基本上各种书籍资料都是这么写的。不过,我们可以写多个 Main 函数,然后在项目文件中设置应该选择哪一个 Main 函数。

你可能会觉得这样没有什么用,不过如果你的应用程序在不同的编译条件下有不同的启动代码,或者你需要持续去大范围修改启动代码,那么做一个 Main 函数的选择器是一个不错的选择。


本为内容

      • 在哪里选择 Main?
      • 我们准备一个 WPF 程序
      • 根据启动对象的不同,控制不同的启动流程
      • 将不同的文件换成不同的条件编译符

在哪里选择 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>

你可以通过阅读 将 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);
            }
        }
    }

相关文章:

  • WPF 的 ElementName 在 ContextMenu 中无法绑定成功?试试使用 x:Reference!
  • WPF 中的 NameScope
  • Windows 下的高 DPI 应用开发(UWP / WPF / Windows Forms / Win32)
  • 技术、产品、交流、思考 - 微软技术暨生态大会 2018
  • .NET/C# 反射的的性能数据,以及高性能开发建议(反射获取 Attribute 和反射调用方法)
  • .NET/C# 异常处理:写一个空的 try 块代码,而把重要代码写到 finally 中(Constrained Execution Regions)
  • WindowsXamlHost:在 WPF 中使用 UWP 控件库中的控件
  • WindowsXamlHost:在 WPF 中使用 UWP 的控件(Windows Community Toolkit)
  • .NET/C# 在代码中测量代码执行耗时的建议(比较系统性能计数器和系统时间)
  • 编写 Target 检测 MSBuild / dotnet build 此次编译是否是差量编译
  • 使用 Win2D 绘制带图片纹理的圆(或椭圆)
  • Win2D 中的游戏循环:CanvasAnimatedControl
  • 使用 Windows 10 中的加速度计(Accelerometer,重力传感器)
  • 用 dotTrace 进行性能分析时,各种不同性能分析选项的含义和用途
  • 如何创建一个基于 .NET Core 3 的 WPF 项目
  • 【面试系列】之二:关于js原型
  • CAP理论的例子讲解
  • JavaScript类型识别
  • Otto开发初探——微服务依赖管理新利器
  • python_bomb----数据类型总结
  • Python语法速览与机器学习开发环境搭建
  • React-生命周期杂记
  • Redis学习笔记 - pipline(流水线、管道)
  • Vue组件定义
  • 分享自己折腾多时的一套 vue 组件 --we-vue
  • 基于 Ueditor 的现代化编辑器 Neditor 1.5.4 发布
  • 聊一聊前端的监控
  • 前端面试题总结
  • 通过获取异步加载JS文件进度实现一个canvas环形loading图
  • 我的zsh配置, 2019最新方案
  • 异常机制详解
  • 用jQuery怎么做到前后端分离
  • AI算硅基生命吗,为什么?
  • ​Linux Ubuntu环境下使用docker构建spark运行环境(超级详细)
  • #[Composer学习笔记]Part1:安装composer并通过composer创建一个项目
  • #if #elif #endif
  • #NOIP 2014# day.2 T2 寻找道路
  • #我与Java虚拟机的故事#连载07:我放弃了对JVM的进一步学习
  • (2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (arch)linux 转换文件编码格式
  • (javascript)再说document.body.scrollTop的使用问题
  • (动态规划)5. 最长回文子串 java解决
  • (二十四)Flask之flask-session组件
  • (十三)Maven插件解析运行机制
  • (转)PlayerPrefs在Windows下存到哪里去了?
  • (转)Windows2003安全设置/维护
  • (转)人的集合论——移山之道
  • (轉貼) 蒼井そら挑戰筋肉擂台 (Misc)
  • .\OBJ\test1.axf: Error: L6230W: Ignoring --entry command. Cannot find argumen 'Reset_Handler'
  • .bat批处理(五):遍历指定目录下资源文件并更新
  • .class文件转换.java_从一个class文件深入理解Java字节码结构
  • .NET CORE 2.0发布后没有 VIEWS视图页面文件
  • .NET Core MongoDB数据仓储和工作单元模式封装
  • .NET MVC、 WebAPI、 WebService【ws】、NVVM、WCF、Remoting
  • .NET 使用 XPath 来读写 XML 文件