2.1.5 C#8.0教程 - 预处理指令 预处理指令的位置
liebian365 2024-10-18 09:35 47 浏览 0 评论
如果你熟悉 C语言 或它的直系衍生语言,你一定了解预处理器的基本概念。那么 C# 是否提供了预处理器?结论是:它没有单独的预处理阶段,也不提供宏。然而,它确实有一些类似于 C语言 预处理器提供的指令,尽管它的选择非常有限。尽管 C# 不像 C 一样有完整的预处理阶段,但是这些被称为预处理指令。本课将会提到一些常用的指令。
- 编译符号
- #error 与 #warning
- #line
- #pragma
- #nullable
- #region and #endregion
编译符号
C# 提供了一个 #define 指令,可以让你定义编译符号。这些符号通常与 #if 指令一起使用,以不同的方式针对不同的情况编译代码。例如,你可能希望某些代码只出现在调试中,或者你可能需要在不同的平台上使用不同的代码来实现特定的效果。通常,你不会使用 #define 指令,但更常见的是通过编译器构建设置来定义编译符号。要对此进行控制,可以直接打开 .csproj 项目文件,在 PropertyGroup 中的 DefineConstants 元素中定义需要的值。
.NET SDK 定义了某些默认符号。它支持两种配置:调试Debug 和 发布Release。它在调试配置中定义 DEBUG 编译符号,而在发布中定义了 RELEASE。在两种配置中定义了一个两者共有的称为 TRACE 的符号。某些项目类型使用额外的符号。例如,针对 .NET Standard 2.0 将同时定义NETSTANDARD 和 NETSTANDARD2_0。
编译符号通常与 #if、#else、#elif 和 #endif 指令一起使用(#elif是else if的缩写)。例5.1 使用其中一些指令来确保某些代码行只在调试版本中编译。(你还可以编写#if false来完全防止代码段被编译。这通常只是作为一种临时措施,是注释的替代方法,避免了试图嵌套注释时的词汇陷阱。)
// 示例 5.1
static void Main(string[] args)
{
#if RELEASE
Console.WriteLine("RELEASE ...");
#endif
#if DEBUG
Console.WriteLine("DEBUG ...");
#endif
}
c#提供了一种更微妙的机制来支持这类事情,称为条件方法。编译器识别.NET类库定义的称为ConditionalAttribute的属性,并为其提供特殊的 编译时compile-time 行为。你可以给任何方法用此属性注释。例5.2 使用它表示只有在定义了调试编译符号时才应该使用带注释的方法。
// 示例 5.2
static void Main(string[] args)
{
ShowDebugInfo();
ShowReleaseInfo();
}
[System.Diagnostics.Conditional("DEBUG")]
static void ShowDebugInfo()
{
Console.WriteLine("Conditional DEBUG ...");
}
[System.Diagnostics.Conditional("RELEASE")]
static void ShowReleaseInfo()
{
Console.WriteLine("Conditional RELEASE ...");
}
注意对比图2 、图3的两个条件方法,一个是在 Debug 阶段运行的,一个是在 Release 阶段运行的。
特别要注意输出面板,分别输出了 “Conditional DEBUG ...” 和 “Conditional RELEASE ...”。说明条件方法在起作用。
另外要特别理解的是,如果您编写的代码调用了这样注释的方法,c#编译器将在没有定义相关符号的编译中忽略该调用。因此,如果你编写代码来调用这两个 ShowDebugInfo / ShowReleaseInfo 方法,编译器会在编译中删除另外一个调用方法,但不会使用指令使代码混乱。
.NET类库中的 Debug类 和 Trace类 在System.Diagnostics命名空间使用此特性。Debug类提供了各种以调试编译符号为条件的方法,而Trace类有以跟踪为条件的方法。如果保留新C#项目的默认设置,那么通过Trace类生成的任何诊断输出都将在调试和发布版本中可用,但是调用Debug类上的方法的任何代码都不会编译到发布版本中。
#error 与 #warning
C# 允许您使用 #error 和 #warning 指令来选择生成编译器错误或警告。这些通常在条件区域内使用,如例2-30所示,当然一个无条件执行的 #warning 可以用来提醒自己还没有编写一些特别重要的代码。
// 示例 5.3
static void Main(string[] args)
{
#warning 当您运行此代码后,您有权保持沉默,您所说的一切将会作为呈堂证供!
#error 停止交易,有内鬼!
}
如图4.所示,#warning 会导致出现警告提示,但仍会继续编译过程。但 #error 除了会出现错误提示外,还会导致编译失败,或者称 build 错误,修复后才能继续。
#line
在生成的代码中,#line指令非常有用。当编译器生成错误或警告时,它通常会声明问题发生的位置,提供文件名、行号和该行内的偏移量。但是,如果问题代码是使用其他文件作为输入自动生成的,并且其他文件包含问题的根本原因,那么在输入文件中报告错误可能比在生成的文件中报告错误更有用。一个#line指令可以指示c#编译器就像错误发生在指定的行号上一样,也可以选择像错误发生在一个完全不同的文件中一样。例5.4 展示了如何使用它。指令后的错误将被报告,好像它来自一个名为footbar.cs的文件的第999行。
static void Main(string[] args)
{
Inttt x;
#line 999 "footbar.cs"
Inttt x;
}
如图5.所示,虽然 Program.cs 的第9行、第11行出现两处明显的语法错误。但提示出现的文件及所在位置却明显不同。请特别关注橙色箭头与白色箭头所示的区别。
另外,文件名部分是可选的,行号也可以伪造,一切都只是注释。您可以通过编写#line default来告诉编译器恢复报告正常的警告和错误状态,否则后面出现的一切错误都会以这个基础继续。
#pragma
#pragma 指令提供了两个特性:它可以用来禁用选定的编译器警告,还可以用来覆盖编译器放入它生成的包含调试信息的 .pdb 文件中的校验和值。这两种方法主要是为代码生成场景设计的,不过它们有时也可以用于禁用普通代码中的警告。例5.5展示了如何使用 #pragma 来防止您声明但没有继续使用的变量而弹出的警告信息。
static void Main(string[] args)
{
int x;
#pragma warning disable CS0168
int y;
}
如图6.所示,在启用 #pragma 指令前,会提示警告变量x未使用。但在启用后,变量y的警告提示未显示。
通常应该避免禁用警告。此特性在生成的代码中非常有用,因为代码生成通常会创建不经常使用的项,而pragmas可能提供了获得干净编译的唯一方法。但是当您手工编写代码时,通常应该能够首先避免警告。
总的来说,如果不是强迫症患者,请勿使用该功能。当然,#pragmas 还有一些其他的能力,比如说为 C# 8.0 中添加的新特性 nullable 等。但对于初学者来说,好像暂时无所谓……但毕竟课题是 C# 8.0 教程,所以我们不得不说一些关于 8.0 的新知识。如下:
#nullable
C# 8.0添加了一个新的指令 #nullable,它允许对可空的注释上下文进行精细控制。这是之后章节中描述的可空引用特性的一部分。(这与之前 #pragmas 中描述的 nullable 警告控件没有重叠,因为我们控制是否启用了nullability注释,与是否启用了与这些注释关联的警告无关)
#region and #endregion
最后,我们有两个不做任何事情的预处理指令。如果你写了#region指令,编译器做的唯一一件事就是确保它们有或者响应#endregion指令。不匹配会导致编译器错误,但是编译器会忽略正确配对的#region和#endregion指令。区域可以嵌套。
这些指令的存在完全是为了便于选择识别它们的文本编辑器。Visual Studio 或 Visual Studio Code 等 C#编辑器使用这组指令可将其包含的代码部分折叠为屏幕上的一行。c#编辑器自动允许扩展和折叠某些特性,比如类定义、方法和代码块(称为outline的特性)。如果您使用这两个指令定义区域,它还将允许扩展和折叠这些区域。
如果将鼠标悬停在折叠区域上,Visual Studio Code 等多数C#编辑器将显示工具提示,显示该区域的内容。
如图7.所示,因为增加了一组 #region 指令,则会在行号后面自动增加出 折叠按钮 (红色箭头所示),会将从 #region 开始,到 #endregion 结束的代码块折叠起来。
虽然代码块 { } 符号也可实现折叠效果,但区别在于,编译器会完全忽略 #region,但 { } 对于编译器来说是有意义的,所以不是完全等同。
本课小结
本课所提到预编译指令,不能给我们的程序带来更多的功能,也无法提高代码执行效率。更多是为代码编辑者使用。大家了解即可,未来读程序时肯定会遇到,可以再来细读。
下节开始数据类型……
相关推荐
- Markdown 常用语法总结(markdown示例)
-
头条不能以代码模式查看,所以分两部分来写:效果、语法。效果和语法部分一一对应,最好自己把语法复制下来保存为.md用md编辑器打开。...
- rsync命令详解(rsync命令详解 -X)
-
1.rsync简介rsync是linux系统下的数据镜像备份工具。使用快速增量备份工具...
- Linux操作系统安全配置(linux系统的安全配置有哪些方面)
-
一、服务相关命令systemctlenable服务名#开机自启动systemctldisable服务名#禁用开机自启动...
- 成功尝试在NetBSD9.0中安装Mate Desktop环境记录
-
NETBSD系统桌面安裝系統...
- 使用OpenLDAP集中式认证(openresty集群)
-
1OpenLDAP入门1.1什么是LDAP?1.2我不理解。什么是目录?1.3信息结构是什么样?...
- 在 Ubuntu 22.04 上安装和配置 VNC 远程桌面
-
环境Ubuntu22.04.2LTSx86_64Step-1安装桌面环境...
- Zabbix入门操作指南(zabbix怎么使用)
-
上篇:安装与配置一.概述在开始之前,一些概念和定义需要我们提前了解一下(以下内容摘自官方网站)。...
- 从0开始学习KVM-KVM学习笔记(6)- CentOS远程桌面连接
-
CentOS...
- systemd service之:服务配置文件编写(2)
-
接下来会通过示例来描述不同ServiceType值的应用场景。在此之前,强烈建议先阅读前后台进程父子关系和daemon类进程来搞懂进程之间的关系和Daemon类进程的特性。systemdservi...
- Linux项目开发,你必须了解Systemd服务!
-
1.Systemd简介...
- Oracle 数据库日常巡检之检查数据库安全性
-
在本节主要检查Oracle数据库的安全性,包含:检查系统安全信息,如系统账户,系统防火墙策略,密码策略等。...
- 「分享」非常全面的CentOS7系统安全检测和加固脚本
-
CentOS7系统检测和加固脚本脚本来源:...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)