.NET中异常处理的最佳实践(译)

原文地址:点击打开链接

本文翻译自CodeProject上的一篇文章,原文地址。

目录

介绍

做最坏的打算

提前检查

不要信任外部数据

可信任的设备:摄像头、鼠标以及键盘

 “写操作”同样可能失效

安全编程

不要抛出“new Exception()”

不要将重要的异常信息存储在Message属性中

每个线程要包含一个try/catch块

捕获异常后要记录下来

不要只记录Exception.Message的值,还需要记录Exception.ToString()

要捕获具体的异常

不要中止异常上抛

清理代码要放在finally块中

不要忘记使用using

不要使用特殊返回值去表示方法中发生的异常

不要使用“抛出异常”的方式去表示资源不存在

不要将“抛出异常”作为函数执行结果的一种

可以使用“抛出异常”的方式去着重说明不能被忽略的错误

不要清空了堆栈跟踪(stack trace)信息

异常类应标记为Serializable

使用”抛出异常”代替Debug.Assert

每个异常类至少包含三个构造方法

不要重复造轮子

VB.NET

模拟C#中的using语句

不要使用非结构化异常处理(On Error goto)

总结

介绍

“我的软件程序从来都不会出错”。你们相信吗?我几乎可以肯定所有人都会大喊我是个骗子。“软件程序几乎不可能没有bug!”

事实上,开发一个可信任、健全的软件程序并不是不可能的事情。注意我这里并不是指那些用于控制核电站的软件,而是指一些常见的商业软件,这些软件可能运行在服务器上,又或者PC机上,它们可以连续工作几个星期甚至几个月都不会出现重大问题。可以猜到,我刚才的意思是指软件有一个比较低的出错率,你可以迅速找到出错的原因并快速修复,并且出现的错误并不会造成重大的数据损坏。

换句话说,我的意思是指软件比较稳定。

软件中有bug是可以理解的。但是如果是经常出现的bug,并且因为没有足够的提示信息导致你不能迅速修复它,那么这种情况是不可被原谅的。

为了更好地理解我上面所说的话,我举个例子:我经常看见无数的商业软件在遇到硬盘不足时给出这样的错误提示:

“更新客户资料失败,请与系统管理员联系然后重试”。

除了这些外,其他任何信息都没有被记录。要搞清楚到底什么原因引起的这个错误是一件非常耗时的过程,在真正找到问题原因之前,程序员可能需要做各种各样的猜测。

注意在这篇文章中,我主要讲怎样更好地处理.NET编程中的异常,并没有打算讨论怎样显示合适的“错误提示信息”,因为我觉得这个工作属于UI界面开发者,并且它大部分依赖于UI界面类型以及最终使用软件的用户。比如一个面向普通用户的文本编辑器的“错误提示信息”应该完全不同于一个Socket通信框架,因为后者直接用户是程序员。

 

做最坏的打算

遵守一些基本的设计原则可以让你的程序更加健全,并且当错误发生时,能够提升用户体验。我这里说到的“提升用户体验”并不是指错误的提示窗体能够让用户高兴,而是指发生的错误不会损坏原有数据,不会让整个电脑崩溃。如果你的程序遇到硬盘不足的错误,但是程序不会造成其他任何负面效果(仅仅提示错误信息,不会引起其他问题,译者注),那么这时候就提升了用户体验。

提前检查

强类型检查和验证是避免bug发生的有力方法。你越早发现问题,就越早修复问题。几个月后再想搞清楚“为什么InvoiceItems表中的ProductID栏会存在一个CustomerID数据?”是一件不太容易并且相当恼火的事情。如果你使用一个类代替基本类型(如int、string)去存储客户(Customer)的数据的话,编译器就不会允许刚才那件事情(指将CustomerID和ProductID混淆,译者注)发生。

不要信任外部数据

外部数据是不可靠的,我们的软件程序在使用它们之前必须严格检查。无论这些外部数据来自于注册表、数据库、硬盘、socket还是你用键盘编写的文件,所有这些外部数据在使用前必须严格进行检查。很多时候,我看到一些程序完全信任配置文件,因为开发这些程序的程序员总是认为没有人会编辑配置文件并损坏它。

可信任的设备:摄像头、鼠标以及键盘

当你需要用到外部数据时,你可能会遇到以下情况:

  1)没有足够的安全权限

  2)数据不存在

  3)数据不完整

  4)数据完整,但是格式不对

不管数据源是注册表中的某个键、一个文件、socket套接字、数据库、Web服务或者串口,以上情况均可能发生。所有的外部数据总会有失效的可能。

“写操作”同样可能失效

不可信任的数据源同样也是一种不可信任的数据仓库。当你存储数据时,相似情况依旧可能会发生:

  1)没有足够的安全权限

  2)设备不存在

  3)没有足够的空间

  4)存储设备发生了物理错误

这就是为什么一些压缩软件在工作时创建了一个临时文件,当工作完成后再重命名,而不是直接修改源文件。原因是如果硬盘损坏(或者软件异常)可能导致原始数据丢失。(译者遇见过这种情况,备份数据时断电,结果原来的旧版备份被损坏了,译者注)

 

安全编程

我的一个朋友告诉我:一个好的程序员从来不会在他的程序中编写糟糕的代码。我觉得这只是成为一个好程序员的必要条件而不是充分条件。下面我整理了一些当你进行异常处理时,可能会编写的“糟糕代码”:

不要抛出“new Exception()”

请别这样做。Exception是一个非常抽象的异常类,捕获这类异常通常会产生很多负面影响。通常情况下应该定义我们自己的异常类,并且需要区分系统(framework)抛出的异常和我们自己抛出的异常。

不要将重要的异常信息存储在Message属性中

异常都封装在类中。当你需要返回异常信息时,请将信息存储在一些单独的属性中(而不要放在Message属性中),否则人们很难从Message属性中解析出他们需要的信息。比如当你仅仅需要纠正一下拼写错误,如果你将错误信息和其它提示内容一起以String的形式写在了Message属性中,那么别人该怎样简单地获取他们要的错误信息呢?你很难想象到他们要做多少努力。

每个线程要包含一个try/catch块

一般异常处理都放在了程序中一个比较集中的地方。每个线程都需要有一个try/catch块,否则你会漏掉某些异常从而出现难以理解的问题。当一个程序开启了多个线程去处理后台任务时,通常你会创建一个类型来存储各个线程执行的结果。这时候请不要忘记了为类型增加一个字段来存储每个线程可能发生的异常,否则的话,主线程不会知道其他线程的异常情况。在一些“即发即忘”的场合(意思主线程开启线程后不再关心线程的运行情况,译者注),你可能需要将主线程中的异常处理逻辑复制一份到你的子线程中去。

捕获异常后要记录下来

不管你的程序是使用何种方式记录日志——log4net、EIF、Event Log、TraceListeners或者文本文件等,这些都不重要。重要的是:当你遇到异常后,应该在某个地方将它记录在日志中。但是请仅仅记录一次,否则的话,你最后会得到一个非常大的日志文件,包含了许多重复信息。

不要只记录Exception.Message的值,还需要记录Exception.ToString()

当我们谈到记录日志时,不要忘了我们应该记录Exception.ToString()的值,而不是Exception.Message。因为Exception.ToString()包含了“堆栈跟踪”(stack trace)信息,内部异常信息以及Message。通常这些信息非常重要,而如果你只记录Exception.Message的话,你只可能看到类似“对象引用未指向堆中实例”这样的提示。

要捕获具体的异常

如果你要捕获异常,请尽可能的捕获具体异常(而非Exception)。

我经常看见初学者说,一段好的代码就是不能抛出异常的代码。其实这说法是错误的,好的代码在必要时应该抛出相应的异常,并且好的代码只能捕获它知道该怎么处理的异常(注意这句话,译者注)。

下面的代码作为对这条规则的说明。我敢打赌编写下面这段代码的那个家伙看见了会杀了我的,但是它确实是摘取自真实编程工作中的一段代码。

第一个类MyClass在一个程序集中,第二个类GenericLibrary在另一个程序集中。在开发的机器上运行正常,但是在测试机器上却总是抛出“数据不合法!”的异常,尽管每次输入的数据都是合法的。

你们能说说这是为什么吗?

public class MyClass{    public static string ValidateNumber(string userInput)    {        try        {            int val = GenericLibrary.ConvertToInt(userInput);            return "Valid number";        }        catch (Exception)        {            return "Invalid number";        }    }}public class GenericLibrary{    public static int ConvertToInt(string userInput)    {        return Convert.ToInt32(userInput);    }}

这个问题的原因就是异常处理不太具体。根据MSDN上的介绍,Convert.ToInt32方法仅仅会抛出ArgumentException、FormatException以及OverflowException三个异常。所以,我们应该仅仅处理这三个异常。

问题发生在我们程序安装的步骤上,我们没有将第二个程序集(GenericLibrary.dll)打包进去。所以程序运行后,ConvertToInt方法会抛出FileNotFoundException异常,但是我们捕获的异常是Exception,所以会提示“数据不合法”。

不要中止异常上抛

最坏的情况是,你编写catch(Exception)这样的代码,并且在catch块中啥也不干。请不要这样做。

清理代码要放在finally块中

大多数时候,我们只处理某一些特定的异常,其它异常不负责处理。那么我们的代码中就应该多一些finally块(就算发生了不处理的异常,也可以在finally块中做一些事情,译者注),比如清理资源的代码、关闭流或者回复状态等。请把这当作习惯。

有一件大家容易忽略的事情是:怎样让我们的try/catch块同时具备易读性和健壮性。举个例子,假设你需要从一个临时文件中读取数据并且返回一个字符串。无论什么情况发生,我们都得删除这个临时文件,因为它是临时性的。

让我们先看看最简单的不使用try/catch块的代码:

string ReadTempFile(string FileName){    string fileContents;    using (StreamReader sr = new StreamReader(FileName))    {        fileContents = sr.ReadToEnd();    }    File.Delete(FileName);    return fileContents;}

这段代码有一个问题,ReadToEnd方法有可能抛出异常,那么临时文件就无法删除了。所以有些人修改代码为:

string ReadTempFile(string FileName){    try    {        string fileContents;        using (StreamReader sr = new StreamReader(FileName))        {            fileContents = sr.ReadToEnd();        }        File.Delete(FileName);        return fileContents;    }    catch (Exception)    {        File.Delete(FileName);        throw;    }}

这段代码变得复杂一些,并且它包含了重复性的代码。

那么现在让我们看看更简介更健壮的使用try/finally的方式:

string ReadTempFile(string FileName){    try    {        string fileContents;        using (StreamReader sr = new StreamReader(FileName))        {            fileContents = sr.ReadToEnd();        }        File.Delete(FileName);        return fileContents;    }    catch (Exception)    {        File.Delete(FileName);        throw;    }}

变量fileContents去哪里了?它不再需要了,因为返回点在清理代码前面。这是让代码在方法返回后才执行的好处:你可以清理那些返回语句需要用到的资源(方法返回时需要用到的资源,所以资源只能在方法返回后才能释放,译者注)。

不要忘记使用using

仅仅调用对象的Dispose()方法是不够的。即使异常发生时,using关键字也能够防止资源泄漏。(关于对象的Dispose()方法的用法,可以关注我的书,有一章专门介绍。译者注)

不要使用特殊返回值去表示方法中发生的异常

因为这样做有很多问题:

  1)直接抛出异常更快,因为使用特殊的返回值表示异常时,我们每次调用完方法时,都需要去检查返回结果,并且这至少要多占用一个寄存器。降低代码运行速度。

  2)特殊返回值能,并且很可能被忽略

  3)特殊返回值不能包含堆栈跟踪(stack trace)信息,不能返回异常的详细信息

  4)很多时候,不存在一个特殊值去表示方法中发生的异常,比如,除数为零的情况:

public int pide(int x, int y){    return x / y;}

不要使用“抛出异常”的方式去表示资源不存在

微软建议在某些特定场合,方法可以通过返回一些特定值来表示方法在执行过程中发生了预计之外的事情。我知道我上面提到的规则恰恰跟这条建议相反,我也不喜欢这样搞。但是一些API确实使用了某些特殊返回值来表示方法中的异常,并且工作得很好,所以我还是觉得你们可以谨慎地遵循这条建议。

我看到了.NET Framework中很多获取资源的API方法使用了特殊返回值,比如Assembly.GetManifestStream方法,当找不到资源时(异常),它会返回null(不会抛出异常)。

不要将“抛出异常”作为函数执行结果的一种

这是一个非常糟糕的设计。代码中包含太多的try/catch块会使代码难以理解,恰当的设计完全可以满足一个方法返回各种不同的执行结果(绝不可能到了必须使用抛出异常的方式才能说明方法执行结果的地步,译者注),如果你确实需要通过抛出异常来表示方法的执行结果,那只能说明你这个方法做了太多事情,必须进行拆分。(这里原文的意思是,除非确实有异常发生,否则一个方法不应该仅仅是为了说明执行结果而抛出异常,也就是说,不能无病呻呤,译者注)

可以使用“抛出异常”的方式去着重说明不能被忽略的错误

我可以举个现实中的例子。我为我的Grivo(我的一个产品)开发了一个用来登录的API(Login),如果用户登录失败,或者用户并没有调用Login方法,那么他们调用其他方法时都会失败。我在设计Login方法的时候这样做的:如果用户登录失败,它会抛出一个异常,而并不是简单的返回false。正因为这样,调用者(用户)才不会忽略(他还没登录)这个事实。

不要清空了堆栈跟踪(stack trace)信息

堆栈跟踪信息是异常发生时最重要的信息,我们经常需要在catch块中处理一些异常,有时候还需要重新上抛异常(re-throw)。下面来看看两种方法(一种错误的一种正确的):

错误的做法:

try{    // Some code that throws an exception}catch (Exception ex){    // some code that handles the exception    throw ex;}

为什么错了?因为当我们检查堆栈跟踪信息时,异常错误源变成了“thorw ex;”,这隐藏了真正异常抛出的位置。试一下下面这种做法:

try{    // Some code that throws an exception}catch (Exception ex){    // some code that handles the exception    throw;}

有什么变化没?我们使用“throw;”代替了“throw ex;”,后者会清空原来的堆栈跟踪信息。如果我们在抛出异常时没有指定具体的异常(简单的throw),那么它会默认地将原来捕获的异常继续上抛。这样的话,上层代码捕获的异常还是最开始我们通过catch捕获的同一个异常。

拓展阅读:

C# 异常处理(Catch Throw)IL分析

异常类应标记为Serializable

很多时候,我们的异常需要能被序列化。当我们派生一个新的异常类型时,请不要忘了给它加上Serializable属性。谁会知道我们的异常类会不会用在Remoting Call或者Web Services中呢?

使用”抛出异常”代替Debug.Assert

当我们发布程序后,不要忘了Debug.Assert将会被忽略。我们在代码中做一些检查或者验证工作时,最好使用抛出异常的方式代替输出Debug信息。

  将输出Debug信息这种方式用到单元测试或者那些只需要测试当软件真正发布后确保不会出错的场合。

每个异常类至少包含三个构造方法

做这件事相当简单(直接从其他的类型粘贴拷贝相同的代码即可),如果你不这样做,那么别人在使用你编写的异常类型时,很难遵守上面给出的一些规则的。

  我指的哪些构造方法呢?这三个构造方法可以参见这里。

 

不要重复造轮子

已经有很多在异常处理方面做得比较好的框架或库,微软提供的有两个:

Exception Management Application Block

Microsoft Enterprise Instrumentation Framework

注意,如果你不遵守我上面提到的一些规则,这些库对你来讲可能没什么用。

 

VB.NET

如果你已经读完整篇文章,你就会发现所有的示例代码都是用C#编写的。那是因为C#是我比较喜欢的.NET语言,并且VB.NET有它自己的一些特殊规则。

模拟C#中的using语句

不幸的是,VB.NET中并没有using语句。你每次在释放一个对象的非托管资源时,不得不这样去做:

.NET中异常处理的最佳实践(译)

如果你不按照上面那种方式调用DIspose方法的话,很可能会出现错误(有关Dispose方法的调用,请关注新书。译者注)。

不要使用非结构化异常处理(On Error Goto)

非结构化异常处理也叫“On Error Goto”,Djikstra(艾兹赫尔·戴克斯特拉)在1974年说过“goto语句有害无益”,这已经是30年之前了!请删除你代码中的所有goto式的语句,我向你保证,他们万害无一益。(艾兹赫尔·戴克斯特拉提出了“goto有害论”、信号量和PV原语,解决了有趣的哲学家就餐问题。《软件故事》一书中讲Fortran语言时提到过他。译者注)

 

总结

我希望本篇文章能够让一部分人能够提高他们的编码质量,也希望这篇文章是讨论怎样有效地进行异常处理的开始,并让我们编写的程序更加健壮。

 

译者话:

我有一个缺点,不知道有没有网友跟我一样。我是个慢热型的人,对技术也一样,好多东西流行颠峰时期过去了我才开始有所感觉。主要一是因为我对新鲜东西不太感冒;二是我总感觉原来学习的东西还没有掌握好就换,有点半途而废的意思。其实我也知道这样非常不好,毕竟IT行业是个快速发展的行业,一没跟上步伐就落后了。

.NET中异常处理的最佳实践(译)

正是遇见这样相互矛盾的情况,我在学习知识的时候都是重点学习技术间的通性,所谓通性,即十年、二十年甚至三十年不太会变、不太会没落的东西,如果你现在从事的公司实际开发过程中一直使用某一套框架,你要是死抓着“怎样使用这个框架做出好的系统”不放,那么过几年你可能就落伍了。而如果你研究研究编程中的共性,比如协议、系统间的交互原理等,这些在每个网络通信系统中都会用到,无论是貌似已经过时了的PC程序,还是Web程序,还是当前流行的移动APP,都会用到,而且基本原理都是一样的。看得多了,就发现新东西出来好像是换汤不换药的感觉(稍微夸张:-))

因此,我给那些跟我一样,不太跟随新鲜事物的人、或者那些长期从事某一类固定开发工作的人的建议是:找准技术间的共性,不要停留在技术表面,除非你对新鲜事物足够感兴趣,并且有充分精力。

以上这些话也是我们公司开讨论会时分享的。

作者:周见智
出处:http://www.php.cn/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 

补充:

关于CLR的 “两轮遍历”异常处理策略。
当应用 程序拥有多层嵌套的异常捕获结构时,如果最底层(其实在中间层也一样)发生了异常,CLR将优先在引发异常的那一层去搜索catch语句块,看看有没有“兼容”
此类型异常的处理代码 ,如果没有,就“跳到”上一层去搜索,如果上一层还没有,继续搜索上一层的“上一层”,由此直到应用 程序的最顶层。
这就是CLR处理嵌套异常捕获结构应用程序的“第一轮”遍历—–查找合适的异常处理程序。
如果在某一层找到了异常处理程序,注意,CLR并不会马上执行之,而是回到”事故现场”,再次进行“第二轮”遍历,执行所有“中间”层次的finally 语句块,然后,执行
找到异常处理程序 ,最后,再从本层开始一直遍历到最顶层,执行所有的finally语句块。

 以上就是.NET中异常处理的最佳实践(译)的内容,更多相关内容请关注PHP中文网(www.php.cn)! 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1432317.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月17日 06:28:39
下一篇 2025年12月14日 22:49:31

相关推荐

  • .NET中的六个重要概念:栈、堆、值类型、引用类型、装箱和拆箱

    内容导读 •概述 •当你声明一个变量背后发生了什么?•堆和栈•值类型和引用类型•哪些是值类型,哪些是引用类型?•装箱和拆箱•装箱和拆箱的性能问题一、概述本文会阐述六个重要的概念:堆、栈、值类型、引用类型、装箱和拆箱。本文首先会通过阐述当你定义一个变量之后系统内部发生的改变开始讲解,然后将关注点转移到…

    2025年12月17日 好文分享
    000
  • .Net 垃圾回收和大对象处理

    英文原文:Maoni Stephens,编译:赵玉开(@玉开Sir) CLR垃圾回收器根据所占空间大小划分对象。大对象和小对象的处理方式有很大区别。比如内存碎片整理 —— 在内存中移动大对象的成本是昂贵的,让我们研究一下垃圾回收器是如何处理大对象的,大对象对程序性能有哪些潜在的影响。 大对象堆和垃圾…

    2025年12月17日 好文分享
    000
  • C# 异常处理(Catch Throw)IL分析

    1、catch throw的几种形式及性能影响: private void Form1_Click(object sender, EventArgs e) { try { } catch { throw; } } private void Form1_Load(object sender, Even…

    2025年12月17日
    000
  • C# 中 10 个你真的应该学习(和使用!)的功能

    如果你开始探索c#或决定扩展你的知识,那么你应该学习这些有用的语言功能,这样做有助于简化代码,避免错误,节省大量的时间。    1)async / await 使用async / await-pattern允许在执行阻塞操作时解除UI /当前线程的阻塞。async / await-pattern的工…

    好文分享 2025年12月17日
    000
  • 关于.NET异常处理的思考(上)

    在项目开发中,对于系统和代码的稳定性和容错性都是有对应的要求。实际开发项目中的代码与样例代码的区别,更多的是在代码的运行的稳定性、容错性、扩展性的比较。 因为对于实现一个功能来说,实现功能的核心代码是一样的,可能只是在写法上优化而已,但是在实现某一个操作上使用的类来说,这一点是绝大多数时候是一样的。…

    好文分享 2025年12月17日
    000
  • .NET异常设计原则

    异常是使用.net时必然会遇到的问题,但是,有太多的开发人员没有从api设计的角度考虑这个问题。在大部分工作中,他们自始至终都知道需要捕获什么异常以及哪些异常需要写入全局日志。如果你设计了可以让你正确使用异常的api,则可以显著减少修复缺陷的时间。 谁的错? 异常设计背后的基本理论始于这样一个问题,…

    好文分享 2025年12月17日
    000
  • .NET编程之线程池内幕

    本文通过对.net4.5的threadpool源码的分析讲解揭示.net线程池的内幕,并总结threadpool设计的好与不足。 线程池的作用 线程池,顾名思义,线程对象池。Task和TPL都有用到线程池,所以了解线程池的内幕有助于你写出更好的程序。由于篇幅有限,在这里我只讲解以下核心 概念: 线程…

    好文分享 2025年12月17日
    000
  • .NET下几个服务框架介绍

    简介 在公司的服务多了以后,为了调用上的方便,同时为了以后的服务治理,一般都会使用一些服务框架,这里主要介绍我知道的几个服务框架,简析一下这些服务框架的基本概念。 可投入生产环境使用的 以下两个服务框架,我已经见过有公司投入到生产环境,所以对于稳定性,应该不需要有太大的担心。    ServiceS…

    2025年12月17日
    000
  • .NET多线程编程—并发集合

    并发集合 1 为什么使用并发集合? 原因主要有以下几点: System.Collections和System.Collections.Generic名称空间中所提供的经典列表、集合和数组都不是线程安全的,若无同步机制,他们不适合于接受并发的指令来添加和删除元素。 在并发代码中使用上述经典集合需要复杂…

    2025年12月17日
    000
  • .NET编程中Word/Excel 在线预览

    前言 近日项目中做到一个功能,需要上传附件后能够在线预览。之前也没做过这类似的,于是乎就查找了相关资料,.net实现office文件预览大概有这几种方式: 使用Microsoft的Office组件将文件直接转换为html文件(优点:代码实现最简单,工作强度最小。缺点:效果极差) 使用Microsof…

    2025年12月17日
    000
  • 记一次.NET代码重构(上)

    需求:是这样的,要开发一个短信发送的模板,不同客户可能会使用不同的模板,而不同的客户使用的变量参数也是不同的。 之前为了应急,线上已经完成了一个短信模板发送短信的功能,短信模板表也创建了,而且在表中已经新增了一条记录。我只需要做一个短信模板的增删改查界面就可以了,看上去我的任务挺简单的,老司机应该知…

    2025年12月17日 好文分享
    000
  • 记一次.NET代码重构(下)

    public override bool TryGetMember(GetMemberBinder binder, out object result){ if (!_dictionary.TryGetValue(binder.Name, out result)) { result = null; …

    2025年12月17日
    000
  • 为 Jenkins 配置 .NET 持续集成环境

    去年年底,得益于公司引入 jenkins,让我们在持续集成方面迈出了第一步,本文不赘述如何安装 jenkins,主要关注点在于配置 .net 环境。另外本文是在 windows 环境下安装的 jenkins 进行操作。 一、安装环境 首先我们需要先准备几个安装包,将它们安装到 Windows 上: …

    2025年12月17日
    000
  • c#.net中const和readonly的区别

    (1) readonly和const都是用来标示常量的。(2) 初始化赋值不同。const修饰的常量必须在声明的同时赋值。例如: public class Class1 { public const int MaxValue = 10; //正确声明 public const MInValue; /…

    好文分享 2025年12月17日
    000
  • XML中如何生成XML报表模板_XML生成XML报表模板的方法与示例

    利用XSLT、编程语言或模板引擎可生成XML报表模板:1. XSLT将源XML转换为结构化报表;2. Python等语言通过DOM操作动态构建XML;3. Jinja2等模板引擎支持变量与逻辑控制,实现灵活输出。 在XML中生成XML报表模板,实际上是指利用XML的结构化特性设计一个可复用的数据模板…

    2025年12月17日
    000
  • XML中如何删除指定节点_XML删除指定节点的方法与技巧

    使用DOM、XPath、SAX/StAX或工具库可删除XML指定节点。DOM适合中小文件,通过removeChild()删除目标节点;XPath支持复杂条件精准定位;SAX/StAX流式处理适用于大文件;工具库如ElementTree提供简洁API。选择方法需考虑文件大小与性能需求。 在处理XML文…

    2025年12月17日
    000
  • XML中如何校验XML节点顺序_XML校验XML节点顺序的方法与技巧

    答案:使用XSD的xs:sequence可严格校验XML节点顺序,如FirstName→LastName→Age;若顺序错乱则校验失败。 在XML处理过程中,校验节点顺序是确保数据结构符合预期的重要环节。特别是在与外部系统交互、接口对接或数据导入导出时,严格的节点顺序可能影响解析结果或业务逻辑。虽然…

    2025年12月17日
    000
  • XML中如何处理空白节点_XML处理空白节点的操作步骤

    正确处理XML空白节点需根据解析器设置或编程逻辑过滤非重要空白。例如Java DOM可设setIgnoringElementContentWhitespace(true),Python可预处理移除,.NET可通过PreserveWhitespace=false控制,默认保留空白;也可通过DTD/XS…

    2025年12月17日
    000
  • XML中如何合并XML片段_XML合并XML片段的操作方法与技巧

    正确合并XML片段需先创建统一根节点,再通过编程语言的XML库或XSLT将各片段导入,确保编码、命名空间和属性唯一性,避免字符串拼接以防止结构错误。 在处理XML数据时,经常需要将多个XML片段合并成一个完整的文档。这种操作常见于配置文件整合、数据聚合或服务间通信场景。正确地合并XML片段不仅能保证…

    2025年12月17日
    000
  • XML中如何生成带CDATA节点的XML_XML生成带CDATA节点XML的方法与示例

    使用lxml、Java DOM和C# XmlDocument可生成带CDATA的XML,分别通过etree.CDATA、createCDATASection和CreateCDataSection方法实现,注意避免嵌套及编码问题。 在XML中,CDATA(Character Data)节点用于包裹文本…

    2025年12月17日
    000

发表回复

登录后才能评论
关注微信