.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>

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

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

  1. SQL语句在查询分析器中可以执行,代码中不能执行

    问题:SQL语句在查询分析器中可以执行,代码中不能执行 解答:sql中包含数据库的关键字,将关键字用[]括起来,可以解决. 后记:建数据库的时候尽量避免使用关键字. 例子: sql.Format(&q ...

  2. 示例:WPF中自定义StoryBoarService在代码中封装StoryBoard、Animation用于简化动画编写

    原文:示例:WPF中自定义StoryBoarService在代码中封装StoryBoard.Animation用于简化动画编写 一.目的:通过对StoryBoard和Animation的封装来简化动画 ...

  3. VS中批量删除cs代码中的#region和#endregion

    Visual Studio中如何批量删除cs代码中的#region和#endregion,不删除它们中间的代码,只删除这两个标记及标记的注解的方法.Vs中提供了很强大的文本查找与替换功能,简单的替换只 ...

  4. Xpath在选择器中正确,在代码中返回的是空列表问题

    一.问题: 在进行爬虫的时候我们会用到xpath解析html文件,但是会有一种情况就是在xpath选择器中可以使用,但是在代码中就无法使用的情况. 二.原因: 1.是元素中有tbody的原因,这个元素 ...

  5. JDK中ThreadDump诊断Java代码中的线程死锁问题

    多线程的死锁..死锁不是死了而是线程互相等待... 在项目中可能就是在几十万行的代码中存在一个死锁的问题,如何发现这个问题并且解决这个问题. JavaJDK为我们提供了一个诊断工具叫做ThreadDu ...

  6. c代码中调用c++,c++代码中调用c代码

    注意这里的c调用c++或者c++调用c的意思是.c文件中调用.cpp文件中的代码,或者相反 集成开发环境如vc++6.0或者vs都是通过文件后缀来区别当前要编译的是C代码还是C++代码,然后采用相应的 ...

  7. android中broadcastreceiver的用法-代码中注册

    界面如下:     问题1:点击“解绑广播接收器“后再次点击”解绑广播接收器“后,程序崩溃,log信息如下: 08-04 05:04:35.420: E/AndroidRuntime(5521): F ...

  8. jshint在bat批处理中闪退,代码中无法调用的问题

    先说解决办法:加个call eg: call jshint --version Pause 具体原因有空再更

  9. 在汇编代码中调用C函数

    对于ARM体系来说,不同语言撰写的函数之间相互调用(mix calls)遵循的是 ATPCS(ARM-Thumb Procedure Call Standard),ATPCS主要是定义了函数呼叫时参数 ...

随机推荐

  1. Jquery 简明介绍

    http://www.cnblogs.com/luotianshuai/p/5196997.html http://www.cnblogs.com/liujianzuo888/articles/568 ...

  2. 单台DNS服务器搭建(BIND)

    一.理论层面 1. DNS的出现及演化 网络出现的早期是使用IP地址通讯的,那是就几台主机通讯.但是随着接入网络主机的增多,这种数字标识的地址非常不便于记忆,UNIX上就出现了建立一个叫做hosts的 ...

  3. web http协议

    http协议超文本传输协议 http协议是IOS七层协议的应用层,是基于TCP/IP协议的,为什么还要多一个协议了,其实利用TCP协议也是可以的,但是TCP三次握手后是一直保持连接的,如果单单是c/s ...

  4. 尚未指定报表“Report1”的报表定义

    在做RDLC项目中遇到这样的错误 本地报表处理期间出错. 尚未指定报表“Report1”的报表定义 未将对象引用设置到对象的实例. 解决方案: 打开reportViewer->LocalRepo ...

  5. DB开发之大数据量高并发的数据库优化

    一.数据库结构的设计 如果不能设计一个合理的数据库模型,不仅会增加客户端和服务器段程序的编程和维护的难度,而且将会影响系统实际运行的性能.所以,在一个系统开始实施之前,完备的数据库模型的设计是必须的. ...

  6. ubuntu14.04 安装apache+mysql+php

    1.安装apache sudo apt-get update sudo apt-get install apache2 这时http://你机器的ip,就可以访问了. 2.安装mysql sudo a ...

  7. pyDay10

    内容来自廖雪峰的官方网站. 1.python的赋值语句:a, b, c = x, y, z 相当于 a = x, b = y, c = z.(事实上等式右边是一个tuple) 2.获得genarato ...

  8. elasticsearch的服务器响应异常及解决策略(转)

    详述: 1 _riverStatus Import_fail 问题描述: 发现有个索引的数据同步不完整,在 http://192.168.1.17:9200/_plugin/head/ 在browse ...

  9. ZOJ 3747 Attack on Titans

    http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3747 题意: 现在有n个士兵进行排序,只有G.R.P三种士兵,要求至少有m ...

  10. Codeforces Round #416 (Div. 2) C. Vladik and Memorable Trip

    http://codeforces.com/contest/811/problem/C 题意: 给出一行序列,现在要选出一些区间来(不必全部选完),但是相同的数必须出现在同一个区间中,也就是说该数要么 ...