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# 异常处理时建立在四个关键词之上的:trycatchfinallythrow

  • try:一个 try 块标识了一个将被激活的特定的异常的代码块。后跟一个或多个 catch 块。
  • catch:程序通过异常处理程序捕获异常,catch 关键字表示异常的捕获。
  • finallyfinally 块用于执行给定的语句,不管异常是否被抛出都会执行,如果打开一个文件,不管是否出现异常文件都要被关闭。
  • 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);
      }
   }
}
posted @ 2024-12-01 17:15  上善若泪  阅读(10)  评论(0编辑  收藏  举报