C#基础之预处理器,异常处理
1 预处理器
1.1 简介
1.1.1 定义
预处理器指令(Preprocessor Directives
)指导编译器在实际编译开始之前对信息进行预处理。
通过这些指令,可以控制编译器如何编译文件或编译哪些部分。常见的预处理器指令包括条件编译、宏定义等。
所有的预处理器指令都是以 #
开始,且在一行上,只有空白字符可以出现在预处理器指令之前。预处理器指令不是语句
,所以它们不以分号 ; 结束
。
C# 编译器没有一个单独的预处理器,但指令被处理时就像是有一个单独的预处理器一样。在 C# 中,预处理器指令用于在条件编译中起作用。与 C 和 C++ 不同的是,它们不是用来创建宏。一个预处理器指令必须是该行上的唯一指令。
使用预处理器指令特点:
- 提高代码可读性:使用
#region
可以帮助分隔代码块,提高代码的组织性。 - 条件编译:通过
#if
等指令可以在开发和生产环境中编译不同的代码,方便调试和发布。 - 警告和错误:通过
#warning
和#error
可以在编译时提示开发人员注意特定问题
1.1.2 预处理器指令列表
指令 | 描述 |
---|---|
#define | 定义一个符号,可以用于条件编译 |
#undef | 取消定义一个符号 |
#if | 开始一个条件编译块,如果符号被定义则包含代码块 |
#elif | 如果前面的 #if 或 #elif 条件不满足,且当前条件满足,则包含代码块 |
#else | 如果前面的 #if 或 #elif 条件不满足,则包含代码块 |
#endif | 结束一个条件编译块 |
#warning | 生成编译器警告信息 |
#error | 生成编译器错误信息 |
#region | 标记一段代码区域,可以在IDE中折叠和展开这段代码,便于代码的组织和阅读 |
#endregion | 结束一个代码区域 |
#line | 更改编译器输出中的行号和文件名,可以用于调试或生成工具的代码 |
#pragma | 用于给编译器发送特殊指令,例如禁用或恢复特定的警告 |
#nullable | 控制可空性上下文和注释,允许启用或禁用对可空引用类型的编译器检查 |
1.2 指令示例详解
1.2.1 #define 和 #undef 预处理器
#define
用于定义符号(通常用于条件编译),#undef
用于取消定义符号。
#define
允许定义一个符号,这样,通过使用符号作为传递给 #if
指令的表达式,表达式将返回 true
下面的程序说明了这点:
#define PI
using System;
namespace PreprocessorDAppl
{
class Program
{
static void Main(string[] args)
{
#if (PI)
Console.WriteLine("PI is defined");
#else
Console.WriteLine("PI is not defined");
#endif
Console.ReadKey();
}
}
}
1.2.2 条件指令:#if, #elif, #else 和 #endif
条件指令用于测试符号是否为真。如果为真,编译器会执行 #if
和下一个指令之间的代码。
常见运算符有:== (等于),!= (不等于),&& (与),|| (或)
也可以用括号把符号和运算符进行分组。条件指令用于在调试版本或编译指定配置时编译代码。一个以 #if
指令开始的条件指令,必须显示地以一个 #endif
指令终止。
#define DEBUG
#if DEBUG
Console.WriteLine("Debug mode");
#elif RELEASE
Console.WriteLine("Release mode");
#else
Console.WriteLine("Other mode");
#endif
实例
#define DEBUG
#define VC_V10
using System;
public class TestClass
{
public static void Main()
{
#if (DEBUG && !VC_V10)
Console.WriteLine("DEBUG is defined");
#elif (!DEBUG && VC_V10)
Console.WriteLine("VC_V10 is defined");
#elif (DEBUG && VC_V10)
Console.WriteLine("DEBUG and VC_V10 are defined");
#else
Console.WriteLine("DEBUG and VC_V10 are not defined");
#endif
Console.ReadKey();
}
}
1.2.3 综合示例
#define DEBUG
#if DEBUG
Console.WriteLine("Debug mode");
#elif RELEASE
Console.WriteLine("Release mode");
#else
Console.WriteLine("Other mode");
#endif
#warning This is a warning message
#error This is an error message
#region MyRegion
// Your code here
#endregion
#line 100 "MyFile.cs"
// The next line will be reported as line 100 in MyFile.cs
Console.WriteLine("This is line 100");
#line default
// Line numbering returns to normal
#pragma warning disable 414
private int unusedVariable;
#pragma warning restore 414
#nullable enable
string? nullableString = null;
#nullable disable
2 异常处理
2.1 简介
2.1.1 定义
异常是在程序执行期间出现的问题。C#
中的异常是对程序运行时出现的特殊情况的一种响应,异常提供了一种把程序控制权从某个部分转移到另一个部分的方式。C# 异常处理时建立在四个关键词之上的:try
、catch
、finally
和 throw
。
try
:一个try
块标识了一个将被激活的特定的异常的代码块。后跟一个或多个 catch 块。catch
:程序通过异常处理程序捕获异常,catch
关键字表示异常的捕获。finally
:finally
块用于执行给定的语句,不管异常是否被抛出都会执行,如果打开一个文件,不管是否出现异常文件都要被关闭。throw
:当问题出现时,程序抛出一个异常。使用throw
关键字来完成。
2.1.2 异常类
C# 异常是使用类来表示的。C# 中的异常类主要是直接或间接地派生于 System.Exception
类。
System.ApplicationException
类支持由应用程序生成的异常。所以程序员定义的异常都应派生自该类。
System.SystemException
类是所有预定义的系统异常的基类。
派生自 System.SystemException
类的预定义的异常类:
异常类 | 描述 |
---|---|
System.IO.IOException | 处理 I/O 错误 |
System.IndexOutOfRangeException | 处理当方法指向超出范围的数组索引时生成的错误 |
System.ArrayTypeMismatchException | 处理当数组类型不匹配时生成的错误 |
System.NullReferenceException | 处理当依从一个空对象时生成的错误 |
System.DivideByZeroException | 处理当除以零时生成的错误 |
System.InvalidCastException | 处理在类型转换期间生成的错误 |
System.OutOfMemoryException | 处理空闲内存不足生成的错误 |
System.StackOverflowException | 处理栈溢出生成的错误 |
2.2 异常处理
2.2.1 常规处理
C# 以 try 和 catch 块的形式提供了一种结构化的异常处理方案。使用这些块,把核心程序语句与错误处理语句分离开。
这些错误处理块是使用 try、catch 和 finally 关键字实现的。下面是一个当除以零时抛出异常的实例:
using System;
namespace ErrorHandlingApplication
{
class DivNumbers
{
int result;
DivNumbers()
{
result = 0;
}
public void division(int num1, int num2)
{
try
{
result = num1 / num2;
}
catch (DivideByZeroException e)
{
Console.WriteLine("Exception caught: {0}", e);
}
finally
{
Console.WriteLine("Result: {0}", result);
}
}
static void Main(string[] args)
{
DivNumbers d = new DivNumbers();
d.division(25, 0);
Console.ReadKey();
}
}
}
执行结果:
Exception caught: System.DivideByZeroException: Attempted to divide by zero.
at ...
Result: 0
2.2.2 不指定具体异常
用法 | 特点 | 使用场景 |
---|---|---|
catch | 不指定异常类型 | 捕获所有异常,但无法访问异常对象 |
throw | 重新抛出当前异常,保留原始堆栈信息 | 捕获异常后记录日志或执行其他操作再抛出 |
throw ex | 抛出捕获的异常,但丢失原始堆栈信息 | 不推荐,除非需要自定义异常或修改异常信息 |
2.2.2.1 catch 中不指定异常类型
在 catch 中不指定异常类型,这种方式会捕获所有类型的异常
。
语法如下:
try
{
// 可能引发异常的代码
}
catch
{
// 捕获所有异常
Console.WriteLine("发生了一个异常");
}
特点:
- 捕获所有异常:
catch
块不指定异常类型时,任何异常都会被捕获。 - 不提供异常上下文:因为没有捕获具体的异常对象,无法访问异常的详细信息(如消息、堆栈跟踪等)。
- 使用场景:适合处理一些通用的异常情况,但不建议滥用,因为可能会掩盖真正的问题。
2.2.2.2 throw 不指定异常对象
在 throw
后面可以不指定异常对象。这种方式会重新抛出当前捕获的异常,并保留原始异常的堆栈跟踪信息。
语法如下:
try
{
// 可能引发异常的代码
}
catch (Exception)
{
Console.WriteLine("捕获到异常,但继续抛出");
throw; // 重新抛出当前异常
}
特点:
- 重新抛出原始异常:保留原始异常的堆栈跟踪信息。
- 使用场景:适用于需要在捕获异常后进行一些处理(如记录日志)然后继续将异常传递给上层调用者。
2.2.2.3 throw ex 与 throw 的区别
在 catch
块中,既可以使用 throw;
重新抛出当前异常,也可以使用 throw ex;
抛出捕获的异常对象。
复制代码
try
{
throw new InvalidOperationException("测试异常");
}
catch (Exception ex)
{
Console.WriteLine("捕获异常并重新抛出");
throw; // 重新抛出,保留原始堆栈信息
// throw ex; // 抛出 ex,但堆栈信息会被重置
}
区别:
throw
:重新抛出当前异常,保留原始异常的堆栈跟踪。throw ex
:抛出捕获的异常对象,但会重置堆栈跟踪信息,使得原始异常的来源信息丢失。
2.2.3 使用using
using
语句确保在块结束时自动释放资源,即使发生异常也是如此。这在处理实现了 IDisposable
接口的对象时特别有用,例如文件流、数据库连接等。
using (ResourceType resource = new ResourceType())
{
// 使用资源的代码
}
在这个语法中,ResourceType
必须实现 IDisposable
接口。using
语句会在块结束时自动调用 Dispose
方法,释放资源。
使用 using 语句处理文件读取写入的示例:
using System;
using System.IO;
public class Program
{
public static void Main()
{
string inputPath = "input.txt";
string outputPath = "output.txt";
// C# 8.0 之前版本
using (StreamReader reader = new StreamReader(inputPath))
using (StreamWriter writer = new StreamWriter(outputPath))
// 适用于 C# 8.0 及以上版本
//using (StreamReader reader = new StreamReader(inputPath),writer = new StreamWriter(outputPath))
{
string content = reader.ReadToEnd();
writer.Write(content);
}
}
}
2.3 自定义异常
自定义的异常类是派生自 ApplicationException
类。下面的实例演示了这点:
using System;
namespace UserDefinedException
{
class TestTemperature
{
static void Main(string[] args)
{
Temperature temp = new Temperature();
try
{
temp.showTemp();
}
catch(TempIsZeroException e)
{
Console.WriteLine("TempIsZeroException: {0}", e.Message);
}
Console.ReadKey();
}
}
}
public class TempIsZeroException: ApplicationException
{
public TempIsZeroException(string message): base(message)
{
}
}
public class Temperature
{
int temperature = 0;
public void showTemp()
{
if(temperature == 0)
{
throw (new TempIsZeroException("Zero Temperature found"));
}
else
{
Console.WriteLine("Temperature: {0}", temperature);
}
}
}