c#笔记-异常

news2024/11/24 20:30:52

异常

当一个程序遇到各种各样的问题而无法正常运行时,我们需要知道导致问题的原因,
并根据原因来解决问题。

一种常见的方式是程序给出一串错误码,然后开发人员查找对应的错误信息。
在这里插入图片描述
而在c#中,我们可以在程序内部就处理异常,而不需要用字符串来显示错误信息。
我们只需要使用异常类来创建和抛出异常对象就行了。

异常类

异常是一个表示程序运行时出现的错误或者意外情况的对象。异常对象包含了一些有关错误的信息,
例如错误的类型,错误的消息,错误发生的位置,错误的原因等。

异常对象都继承或间接继承自Exception类。作为所有异常基类的Exception
它提供了一些通用的属性和方法来获取和处理异常对象。

  • Message属性:获取描述异常的消息。
  • Source属性:获取或设置导致异常的应用程序或对象的名称。
  • StackTrace属性:获取调用堆栈上的即时框架。
  • InnerException属性:获取导致当前异常的Exception实例。
  • ToString方法:创建并返回当前异常的字符串表示形式。
  • GetBaseException方法:返回一个Exception,它是一个或多个并发的异常的根本原因。

自定义异常

如果我们想要创建一个自己的异常类,我们需要继承或间接继承Exception类,
并且提供以下几个构造器:

  • 一个无参构造器,调用基类的无参构造器。
  • 一个带有字符串参数的构造器,调用基类的带有字符串参数的构造器,并将字符串作为异常消息传递给基类。
  • 一个带有字符串参数和Exception参数的构造器,调用基类的带有字符串参数和Exception参数的构造器,并将字符串作为异常消息,Exception作为内部异常传递给基类。
  • 一个带有SerializationInfo参数和StreamingContext参数的构造器,调用基类的带有SerializationInfo参数和StreamingContext参数的构造器,并将序列化信息和流上下文传递给基类。

例如,我们可以创建一个分数异常类来表示分数异常:

using System;
using System.Runtime.Serialization;

[Serializable]
class ScoreException : Exception
{
    public ScoreException() : base() { }
    public ScoreException(string message) : base(message) { }
    public ScoreException(string message, Exception inner) : base(message, inner) { }
    protected ScoreException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}

注意我们需要给自定义异常类加上[Serializable]特性,表示这个类可以被序列化,
否则在序列化的过程中遇到这个异常,会被认为在程序中出现了异常。

抛出异常

有时候,我们需要主动地抛出一个异常对象,来表示程序中发生了某种错误或者不符合预期的情况。
这样可以让上层代码知道问题的原因,并且可以选择合适的方式来处理异常。

抛出异常使用throw关键字加上一个异常对象来实现。
可以用new创建一个新的异常对象,也可以使用已经存在的变量。
不使用抛出异常时,异常就是一个普通的值。

class Student
{
	int score;
	public int Score
	{
		get => score;
		set
		{
			if (value < 0)
				throw new ScoreException("分数不能是负数");
			score = value;
		}
	}
}
try
{
	Student st = new Student();
	st.Score = -3;
}
catch (ScoreException e)
{
	Console.WriteLine(e.Message);
}

代码终止

抛出异常语句会终止当前方法的执行,并将异常对象传递给上层方法。
如果上层代码块没有处理这个异常,它也会终止执行并继续传递异常对象。
直到Main方法终止导致程序结束,或者有代码块处理了这个异常为止。

抛出异常语句也具有不可达代码检测的效果。抛出异常语句后面的代码不会被执行。
在有返回值的方法中使用抛出异常来终止方法,不需要返回值。
抛出异常语句可以放在三元运算符或switch表达式中当作一个值使用。

public int Score
	{
		get => score;
		set => score = value < 0 ? throw new ScoreException("分数不能是负数") : value;
	}

如果抛出null的话,那么你会得到一个空引用异常System.NullReferenceException
好的,我按照你的要求,把文章细分为四个小节,并且修改了示例代码。请看下面的内容:

捕获异常

有时候,我们需要捕获并处理一个或多个异常对象,来避免程序崩溃。
我们也可以根据异常对象中包含的信息来进行调试和排错。

对于可能引发异常的代码,我们可以使用try-catch语句块来捕获异常。
try-catch语句块不能省略大括号,并且大括号也有作用域限制。
所以通常来说,需要在try之前声明变量,才能在trycatch中都能访问到。

try中,如果触发了异常,那么try块会终止运行,转而运行catch块。
每个catch块后的括号内,是要捕获的异常类型
每个catch会和触发的异常进行一次类型判断比较,
如果触发的异常是指定类型或由它派生,则匹配成功。

try
{
	int x = 10;
	int y = 0;
	int z = x / y; //触发除零异常
}
catch (ArithmeticException) //只捕获算术异常
{
	throw; //在catch块中抛出异常可以不指示实例,这会把捕获到的异常抛出
}

处理异常

如果我们想处理捕获到的异常对象,我们可以在每个catch块后的括号内声明一个变量。
如果捕获成功,那么这个变量就会赋值为这个异常对象。
然后我们就可以根据这个变量的属性和方法来处理异常。

try
{
	int x = 10;
	int y = 0;
	int z = x / y; //触发除零异常
}
catch (Exception e) //声明变量e,并捕获所有类型的异常
{
	Console.WriteLine("发生了一个异常:");
	Console.WriteLine("类型:" + e.GetType());
	Console.WriteLine("消息:" + e.Message);
	Console.WriteLine("源:" + e.Source);
	Console.WriteLine("堆栈跟踪:" + e.StackTrace);
}

多级判断

有时候,我们需要根据不同类型的异常来执行不同的处理逻辑。
这时候,我们可以使用多级判断来精确捕获异常。

多级判断是指在一个try块后跟多个catch块,并且每个catch块指定不同类型的异常。
它们像if-else if-else一样。上一个catch没有捕获到的异常会由下一个catch块尝试捕获和处理。
直到有一个catch块捕获到,或所有的catch块都没有捕获到。

最后一个catch块可以不指定任何类型,这表示捕获所有类型的异常,就像没有ifelse一样。
这个catch块通常用来处理未知或意外的异常。

try
{
	//可能引发多种异常的代码
}
catch (FileNotFoundException e)
{
	//处理文件不存在的情况
}
catch (IOException e)
{
	//处理其他输入输出异常的情况
}
catch
{
	//处理其他未知异常的情况
}

次要判断

有时候,我们需要根据不同条件的异常来执行不同的处理逻辑。
这时候,我们可以使用次要判断来精确捕获异常。

次要判断是指在一个catch块中,根据异常对象的属性或方法来进行进一步的判断。
这样就可以根据触发的异常对象的具体信息来执行更细致的处理逻辑。

在C#中,我们可以使用when关键字来添加一个布尔表达式作为次要判断的条件。
只有当这个条件为真时,才会进入这个catch块。

try
{
	//可能引发多种异常的代码
}
catch (Exception e) when (e.Message.Contains("文件"))
{
	//处理与文件相关的异常
}
catch (Exception e) when (e.Message.Contains("网络"))
{
	//处理与网络相关的异常
}
catch (Exception e)
{
	//处理其他未知异常
}

finally块

finally块是用于执行一些必须或总是要执行的操作,无论是否发生异常。这些操作通常是一些收尾工作,例如关闭一些打开的文件或网络连接,释放一些占用的资源,恢复一些设置等。

finally块需要配合try块使用。
try块中的代码可能会因为异常而中断不运行,导致一些操作没有完成。
catch块中的代码可能因为没有或没匹配到异常而不运行,导致一些操作没有执行。
为了保证一些必经操作能够执行,我们可以使用finally块来进行收尾。

无论是正常运行完毕try块,还是由catch块捕获异常,都会执行finally块。
finally块可以直接try块后面,不是一定需要catch块。

使用finally收尾

在finally块中,我们可以写一些必须执行的操作,无论是否发生异常。
例如,我们可以在读取文件后,无论是否发生异常,都要关闭文件流。
这样可以避免文件被锁住,无法被其他程序访问。

FileStream? fs = null; //因为作用域限制,必须在try之前声明才能让他们都能访问
try
{
	fs = new FileStream("test.txt", FileMode.Open);
	//读取文件内容的代码
}
catch (FileNotFoundException e)
{
	Console.WriteLine("文件不存在:" + e.Message);
}
catch (IOException e)
{
	Console.WriteLine("输入输出异常:" + e.Message);
}
finally
{
	fs?.Close(); //关闭文件流 
}

finally块一定执行

finally块一定会被执行,即使在trycatch中有return语句,或是在catch中也触发了异常。

Console.WriteLine(ReadFile("test.txt"));
string ReadFile(string path)
{
	FileStream? fs = null;
	try
	{
		fs = new FileStream(path, FileMode.Open);
		//读取文件内容的代码
		return "文件读取成功";
	}
	catch (FileNotFoundException e)
	{
		Console.WriteLine("文件不存在:" + e.Message);
		return "文件读取失败";
	}
	catch (IOException e)
	{
		Console.WriteLine("输入输出异常:" + e.Message);
		return "文件读取失败";
	}
	finally
	{
		fs?.Close(); //关闭文件流 
        Console.WriteLine("finally还是会执行");
    }
}

在这个代码中,无论是try还是catch中,都有return语句来提前结束方法。
但是无论哪种情况,都会执行finally块中的关闭文件流的操作。
这样就可以避免文件被锁住,无法被其他程序访问。

好的,我按照你的要求,修改了using语句的内容。请看下面的内容:

using语句

有些类实现了IDisposable接口,表示它们可以被释放或关闭。例如文件流或网络流等。
这些类通常有一个Dispose方法来释放占用的资源。
但是如果我们没有调用这些方法,那么资源就会被浪费或占用过久。

为了避免这种情况,我们可以使用using语句来自动调用Dispose方法。
使用using语句必须在声明变量时进行,声明的变量必须实现IDisposable接口。
并且必须是单独的声明语句,
不能是模式匹配的声明模式,析构元组,捕获异常,out参数声明赋值的变量。

使用using语句的好处是可以简化代码,避免忘记调用Dispose方法,
并且可以保证即使发生异常也能正确释放资源。

using System.Net;

using var wc = new WebClient();
//访问微软文档的官网(中文)
var html = wc.DownloadString("https://docs.microsoft.com/zh-cn/");
Console.WriteLine(html);

这段代码等效于:

var wc = new WebClient();
try
{
	//访问微软文档的官网(中文)
	var html = wc.DownloadString("https://docs.microsoft.com/zh-cn/");
	Console.WriteLine(html);
}
finally
{
	wc.Dispose(); //在离开作用域时调用Dispose方法
}

在这个代码中,我们使用using语句来声明和初始化一个WebClient对象。
在using语句的作用域内,我们可以正常使用wc对象。
当离开using语句的作用域时,wc对象会自动调用Dispose方法来关闭网络连接。

非托管资源

.NET会对一些资源进行分配,监管,回收,释放的机制,这种资源称为托管资源。
但是对于非托管资源(即由操作系统或其他程序分配和管理的资源),
.NET就无能为力了。例如,文件句柄,数据库连接等。

如果我们不及时释放非托管资源,那么就会造成资源泄漏和浪费。
程序在关闭时,操作系统会尝试释放这些资源。
但这些资源可能有释放的顺序要求,如果不按要求就会出错。

释放模式

释放模式是一种实现IDisposable接口的推荐方式。
它可以有效地处理托管资源和非托管资源的释放,并且可以支持继承和多态。
释放模式的一般化格式如下:

using System;
using System.Runtime.InteropServices;

class BaseClass : IDisposable
{
    // 标志:Dispose方法是否已经被调用过
    protected bool disposed = false;

    // 实例化一个SafeHandle类型的对象
    SafeHandle handle = new SafeFileHandle(IntPtr.Zero, true);

    // 公开的Dispose方法,供用户手动调用
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // 受保护的虚拟Dispose方法,用于实现释放逻辑
    protected virtual void Dispose(bool disposing)
    {
        if (disposed)
            return;

        if (disposing)
        {
            // 在这里释放其他托管资源
            handle.Dispose();
        }

        // 在这里释放非托管资源
        
        disposed = true;
    }

    // 终结器,用于在垃圾回收器回收对象时调用
    ~BaseClass()
    {
        Dispose(false);
    }
}

释放模式要做的事情有以下几点:

  • 声明一个bool字段,用于标记对象是否已经被释放过。
  • 声明一个SafeHandle类型的字段,用于封装非托管资源的句柄。
  • 实现IDisposable接口的Dispose方法,用于供用户手动调用。在这个方法中,调用一个受保护的虚拟方法Dispose(bool),并传入true作为参数,表示是由用户显式调用的。然后调用GC.SuppressFinalize方法,告诉垃圾回收器不要再调用终结器了。
  • 实现一个受保护的虚拟方法Dispose(bool),用于实现所有资源释放的逻辑。在这个方法中,首先检查对象是否已经被释放过,如果是则直接返回。然后根据传入的参数判断是由用户显式调用还是由垃圾回收器隐式调用。如果是由用户显式调用,则释放所有托管资源和非托管资源。如果是由垃圾回收器隐式调用,则只释放非托管资源。最后将标记字段设为true,表示对象已经被释放过了。
  • 实现一个终结器,用于在垃圾回收器回收对象时调用。在这个方法中,调用受保护的虚拟方法Dispose(bool),并传入false作为参数,表示是由垃圾回收器隐式调用的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/514155.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

改进YOLOv8 | 即插即用篇 | 在 C2F 模块中添加 【SE】 【CBAM】【 ECA 】【CA 】注意力机制 | 附详细结构图

这篇教程是在你默认添加了《改进YOLOv8 | 即插即用篇 | 手把手教你 YOLOv8 添加注意力机制 | 适用于【检测任务】【分类任务】【分割任务】【关键点任务】| 20+ 种全打通!》的代码后进行的后续改进的补充。 1. SE 注意力模块 论文名称:《Squeeze-and-Excitation Networks》 …

AI画图 Ubuntu 20.04.5 LTS x86_64 Docker stable diffusion webui 及 http api接口

资源 Docker镜像 docker pull darkroot1234/ayanami:latest 参考地址&#xff1a; docker一键运行stable diffusion webui&#xff0c;常用插件和功能完备&#xff0c;获得镜像后可打包带走 - 哔哩哔哩 nvidia cuda 驱动 ​​​​​​​Linux x64 (AMD64/EM64T) Display D…

numpy中reshape(1,-1) 和 reshape(-1,1) 的理解

我们都知道**reshape()方法**的作用是将数据按照指定的维度重新组织并返回。也就是reshape&#xff08;行&#xff0c;列&#xff09;可以根据指定的数值将数据转换为特定的行数和列数&#xff0c;就是转换。 但我们经常在大佬的代码中&#xff0c;或者报错提示中发现&#xf…

什么是虚拟机?

我们都知道在 Windows 系统上一个软件包装包是 exe 后缀的&#xff0c;而这个软件包在苹果的 Mac OSX 系统上是无法安装的。类似地&#xff0c;Mac OSX 系统上软件安装包则是 dmg 后缀&#xff0c;同样无法在 Windows 系统上安装。 为什么不同系统上的软件无法安装&#xff0c…

常见4种风险定量分析法

常见的风险定量分析方法主要有&#xff1a; 一、 敏感性分析法 用于确定哪些风险对项目影响最大&#xff0c;将其它不确定因素固定在基准值&#xff0c;再考察某个因素的变化会对目标产生多大的影响。 在这一分析中&#xff0c;检查每一个项目成分的不确定性对所检查项目目标的…

提示工程L1:关键原则

提示工程指南&#xff1a;关键原则 一、 环境配置 chatgpt使用有诸多限制&#xff0c;所以采用国产模型来代替&#xff0c;加载开源的chatGLM模型&#xff0c;使用ChatGLM-6b的INT8版本。 chatGLM6b在LLM匿名竞技场中的排名&#xff1a; import os import torch import war…

大学生找工作防坑指南,收藏10000+

2023年就业季即将抵达&#xff0c;你害怕了吗&#xff1f; 据数据统计&#xff0c;今年的应届生人数再创新高&#xff0c;将达到1158万人&#xff0c;相信同学们看到这串数字已经感到毛骨悚然。然而大学生要面临的挑战不仅这些&#xff0c;最残酷的考验还在最后。 作为初出茅庐…

某些科技外企结束在中国市场直接运营,你如何看?

在新的竞争态势下&#xff0c; 向左走&#xff0c;还是向右走…… 【全球存储观察 &#xff5c; 热点关注】前些天&#xff0c;我发了一篇文章《你如何看LinkedIn“领英职场”将于8月9日起停止中国服务&#xff1f;》引发了业内朋友的热议&#xff0c;大家一致认为&#xff0c…

const和int const 理解笔记(图、代码、讲解)

本文参考了 》C 中的 const & &#xff08;常引用&#xff09;参数 - 知乎 因为博文写的很详细&#xff0c;所以博主按着自己的理解写了demo&#xff0c; 和一些方便自己理解的备注。 自学的时候看到了下面这个代码&#xff0c;所以 const&是啥 首先&#xff0c;一句…

C++中继承的语法和使用

C入门必看 继承的定义及概念概念定义 基类和派生类对象赋值转换继承后的作用域派生类默认的6个成员函数该何去何从&#xff1f; 多继承 面向对象语言3大特性&#xff1a;封装、继承、多态。 继承的定义及概念 概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最…

Dubbo接口测试没你想的那么高大上

主题&#xff1a;Dubbo接口测试没你想的那么高大上 一、Dubbo是什么&#xff1f; Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架一款分布式服务框架、高性能和透明化的RPC远程服务调用方案、SOA服务治理方案下图是阿里巴巴技术解决方案演变图&#xff0c;从单应用->…

卡特兰数三个通项公式的推导

前提条件&#xff1a; 有两种操作&#xff0c;一种操作的次数不能超过另外一个&#xff0c;或者是不能有交集这些操作的合法方案数&#xff0c;通常是卡特兰数 情景&#xff1a; 1&#xff09;n个0和n个1构成的字串&#xff0c;所有的前缀子串1的个数不超过0的个数&#xff…

月薪低于5千元必看,省钱购物攻略,本人亲测有效

作为资深的网购用户&#xff0c;我不允许我的姐妹们还不知道&#xff0c;网上购物如何省钱&#xff1f;如果你是学生党&#xff0c;或者月薪低于5千元&#xff0c;一定要看一看&#xff01;学会了不但能提升生活品质&#xff0c;还能帮你省下好多钱~ 同样的东西&#xff0c;我…

云渲染动画价格一般多少?

云渲染是什么&#xff1f; 云渲染就是通过互联网将用户本地需要渲染的文件上传到云端服务器中&#xff0c;再通过云端庞大的计算机集群资源进行运算操作&#xff0c;帮助用户在云端完成渲染工作后&#xff0c;用户再下载到本地的过程&#xff0c;整个过程操作十分简便。 云渲染…

解释一下泛型擦除?为什么java必须强制?

一、概述&#xff1a; 在解释什么是泛型擦除之前我们得先了解什么是Java泛型。所谓的泛型就是参数化的类型。这就意思着我们可以具体的类型作为一个参数传递给方法、类、接口。 为什么我们需要泛型呢&#xff1f;首先我们都知道在java里&#xff0c;Object就是对象的父类。Ob…

2023 年10款「会议管理」软件对比

在这篇文章中&#xff0c;我们将介绍2023年你可以尝试的10个会议管理软件。我们还将介绍会议管理的基础知识&#xff0c;涉及的步骤&#xff0c;以及如何创建会议议程。 2023 年 10 款会议管理软件对比 会议管理软件解决方案是一种数字工具&#xff0c;可以让个人和团队计划、…

一、【Pytorch笔记】pytorch简介,开发环境安装,搭建

一、下载Anaconda Anaconda包括Conda、Python以及一大堆安装好的工具包&#xff08;其他深度学习里用到的东西&#xff09;&#xff0c;比如&#xff1a;numpy、pandas等。所以下了Anaconda就不用下载Python进入Anaconda官网&#xff1a;https://www.anaconda.com/download/下…

【数字 IC】从底层重新认识 D 触发器、建立时间和保持时间

目录 1. NMOS 和 PMOS 2. MOS 管搭建逻辑门 3. 锁存器和触发器 3.1 交叉耦合反相器 3.2 SR 锁存器 3.3 D 锁存器 3.4 D 触发器 4. D 触发器的建立、保持时间 1. NMOS 和 PMOS MOSFET&#xff08;金属氧化物半导体场效应晶体管&#xff09;或 IGFET&#xff08;绝缘栅场…

Web3中文|又一巨头入局?亚马逊或将正式进军NFT领域

行业的风向总随着巨头公司而动。 自从亚马逊公司推出 NFT 市场的消息传出后&#xff0c;大众的目光就锁定在了亚马逊的身上&#xff0c;作为科技巨头的亚马逊入局 NFT 对整个行业都有着举足轻重的意义。 受 FTX 的崩溃的影响&#xff0c;亚马逊 NFT 市场的推出几经波折&#xf…

InnoDB和MySAM有什么区别?

首先&#xff0c;MySAM和InnoDB都是mysql里面的两个存储引擎&#xff0c;mysql5.5版本之前的存储引擎默认是MySAM&#xff0c;mysql5.5版本以后的存储引擎默认是InnoDB,它们底层数据结构都是基于B树的。 Mylsam存储引擎&#xff1a; Mylsam索引是非聚簇索引&#xff0c;Mylsa…