DDN 关于 C# throw ex; 与 throw; 的问题

Jinglecat 2007-07-19 04:23:42
~每天一个历史遗留问题~

熟悉异常的 C++++ coder,都知道
对于 throw ex; 与 throw; 是有区别的,主要是 CLR 确定异常抛出的起点是有区别的,
如果你不清楚请参考:
《.NET 框架程序设计》Jeffery Richter 李建忠译 chater 18.12 异常堆栈踪迹 P442 的说明

或者见
throw;与throw ex;之间的区别
http://blog.csdn.net/Joy_Zhao/archive/2006/10/27/1352777.aspx


但是,下面的 Button1_Click 即使使用 throw; 也堆栈信息只能跟踪到 Line 14 ,始终无法跟踪到 Line 11,而 Button2_Click 直接到 Line 21

// .aspx.cs
protected void Button1_Click(object sender, EventArgs e)
{
try {
object o = null;
int i = (int)o; // Line 11 // Error, System.NullReferenceException
}
catch {
throw; // Line 14
}
}

protected void Button2_Click(object sender, EventArgs e)
{
object o = null;
int i = (int)o; // Line 21 // Error, System.NullReferenceException
}

// .aspx
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Throw1" />
<asp:Button ID="Button2" runat="server" OnClick="Button2_Click" Text="Throw2" />


我做过很多测试了,对于用 try-catch-throw 捕获 CLR 运行时内部抛出的异常,即出现这种情况,始终无法跟踪到最原始的内部异常起点


何解 ?也许是我遗漏了某个知识点~

谢谢!

...全文
1967 29 打赏 收藏 转发到动态 举报
写回复
用AI写文章
29 条回复
切换为时间正序
请发表友善的回复…
发表回复
Jinglecat 2007-07-20
  • 打赏
  • 举报
回复
这么少人关注异常处理问题?
Jinglecat 2007-07-20
  • 打赏
  • 举报
回复
一个完整的测试示例,诸位可以看看输出的 StackTrace 就明白,各种使用 try-catch-throw 形式的不同点了:

using System;
using System.Collections.Generic;
using System.Text;

namespace TryCatchThrowTestConsole
{
class Program
{
static void Main(string[] args)
{
Tester tester = new Tester();

System.Text.StringBuilder sb = new System.Text.StringBuilder();

/*
* 说明
* 1. 以下 ThrowEx_ 两两成对比较
* 2. ThrowEx7 和 ThrowEx8 演示已知的 throw; 与 throw ex; 的不同,即后者 CLR 重新设置异常的起点
* 位于异常捕获点之上中的调用堆栈信息不会包含在 Exception 的 StackTrace 属性中
* 3. ThrowEx1 和 ThrowEx2 演示本讨论的直接目标:为什么 ThrowEx2 中使用 throw; 堆栈信息也无法包括到 F2_1 这一行,而是直接到 F2_2
* 4. ThrowEx3 和 ThrowEx4 情形同 ThrowEx1 和 ThrowEx2
* 5. ThrowEx5 与 ThrowEx6 效果相同。
* 6. try{//...}catch{throw;}, try{//...}catch(Exception){throw;}, try{//...}catch(Exception ex){throw;} 三者是等价的
*/

try {
tester.ThrowEx1();
}
catch (Exception ex) {
sb.AppendLine("1");
sb.AppendLine(ex.StackTrace); // 堆栈信息如期到 F1_1
sb.AppendLine();
}

try {
tester.ThrowEx2();
}
catch (Exception ex) {
// 堆栈信息只能到 F2_2,我的疑问是为什么不能到 F2_1
// ThrowEx2() 与 ThrowEx1() 唯一区别就是 ThrowEx1() 内部还有一个 try-catch-throw
sb.AppendLine("2");
sb.AppendLine(ex.StackTrace);
sb.AppendLine();

//sb.AppendLine("-----------");
//sb.AppendLine(System.Environment.StackTrace);
//sb.AppendLine("-----------");

System.Diagnostics.StackTrace st = new System.Diagnostics.StackTrace(ex);
sb.AppendLine("-----------");
sb.AppendLine(st.ToString());
sb.AppendLine("-----------");
}

try {
tester.ThrowEx3();
}
catch (Exception ex) {
sb.AppendLine("3");
sb.AppendLine(ex.StackTrace);
sb.AppendLine();
}

try {
tester.ThrowEx4();
}
catch (Exception ex) {
sb.AppendLine("4");
sb.AppendLine(ex.StackTrace);
sb.AppendLine();
}

try {
tester.ThrowEx5();
}
catch (Exception ex) {
sb.AppendLine("5");
sb.AppendLine(ex.StackTrace);
sb.AppendLine();
}

try {
tester.ThrowEx6();
}
catch (Exception ex) {
sb.AppendLine("6");
sb.AppendLine(ex.StackTrace);
sb.AppendLine();
}

try {
tester.ThrowEx7();
}
catch (Exception ex) {
sb.AppendLine("7");
sb.AppendLine(ex.StackTrace);
sb.AppendLine();
}

try {
tester.ThrowEx8();
}
catch (Exception ex) {
sb.AppendLine("8");
sb.AppendLine(ex.StackTrace);
sb.AppendLine();
}

string result = sb.ToString();
System.IO.File.WriteAllText("result.txt", result); // 输出到文件
Console.WriteLine(result); // 输出到控制台

Console.Write("press any key to exit...");
Console.ReadKey();
}

public class Tester
{
public void ThrowEx1()
{
int i = 0, j = 0;
int k = i / j; // F1_1
}

public void ThrowEx2()
{
try {
int i = 0, j = 0;
int k = i / j; // F2_1
}
catch { // 这里 try{}catch(DivideByZeroException){throw;}, try{}catch(DivideByZeroException ex){throw;} 等价
throw; // F2_2
}
}

public void ThrowEx3()
{
throw new DivideByZeroException(); // F3_1
}

public void ThrowEx4()
{
try {
throw new DivideByZeroException(); // F4_1
}
catch {
throw; // F4_2
}
}

public void ThrowEx5()
{
ThrowEx1(); // F5_1
}

public void ThrowEx6()
{
try {
ThrowEx1(); // F6_1
}
catch {
throw; // F6_2
}
}

public void ThrowEx7()
{
try {
ThrowEx1(); // F7_1
}
catch (DivideByZeroException) { // 这里与 try{}catch{throw;}, try{}catch((DivideByZeroException ex){throw;} 等价
throw; // F7_2
}
}

public void ThrowEx8()
{
try {
ThrowEx1(); // F8_1
}
catch (DivideByZeroException ex) {
throw ex; // F8_2
}
}
}
}
}
Jinglecat 2007-07-20
  • 打赏
  • 举报
回复
@commandosvvv(UnknownData)
关于 throw ex; 与 throw; 的区别,我顶楼已经提供了链接文章参考了


@RedGoldFish(红金鱼)
如果真是BUG,不知道 MS 何时修复

对于异常处理情况十分的复杂,对于自己系统的底层框架,有时候我们只是希望捕获异常,然后执行一些资源释放的操作,重新丢出异常,并希望保持堆栈信息,一遍尽快查找错误,

重新包装一次异常,给我们程序员带来了而外工作,有时候,我就真的不希望此异常做说明解释或者说明,直接继续向上抛,留给上层应用程序处理
commandosvvv 2007-07-20
  • 打赏
  • 举报
回复
关于异常处理。。一直很让我头疼。。
我只知道
try{}catch(){} 是直接把异常抛出。

try{}catch(Exception ex){}是要先把异常实例化的,这个过程应该是拿到了很确凿的数据,所以这样是可以追踪到出错的位置的,ex.StackTrace 。
所以如果你想追踪异常的原始位置的话,应该写成后者,但这样效率会有所降低。。

Ivony 2007-07-20
  • 打赏
  • 举报
回复
测试无法重现问题,.NET Framework 2.0
Ivony 2007-07-20
  • 打赏
  • 举报
回复
这个到的确是奇怪,有没有在catch那里把异常输出看看堆栈是什么?
gameboy766 2007-07-20
  • 打赏
  • 举报
回复
关注
RedGoldFish 2007-07-20
  • 打赏
  • 举报
回复
我用.NET 2.0 能重复出LZ的情况. 这里边的问题实际上是Throw不能百分之百保证原始的Stack Trace.

在网上Google了下,这个问题在MS的下边这个Group里讨论过:

microsoft.public.dotnet.languages.csharp

讨论的题目是:

"Bug?! throw vs. throw ex Options"

里边有各种意见,我比较倾向于这是.NET 的一个Bug. 目前的对策也是和LZ一样,重新包装一下Exception以保证Stack Trace显示正确的行号.

commandosvvv 2007-07-20
  • 打赏
  • 举报
回复
学习!~~
不过,请教下,
try{}catch(){throw;} 与try{}catch(Exception ex){throw ex;}
只是 你所说得 “前者 IL 指令是 rethrow 后者 IL 指令是 throw”的区别吗??

还有,什么都不写 自动有系统踢出不可继续进行的error 和使用try{}catch(){throw;}
有什么区别呢??
SassyBoy 2007-07-20
  • 打赏
  • 举报
回复
Study~~
Ivony 2007-07-20
  • 打赏
  • 举报
回复
SQL Helper很垃圾的,推荐你到我的Blog(http://Ivony.cnblogs.com/)上去看看,尽管不知道谁在那里说垃圾,但我想比SQL Helper还是要强一点点的。
Jinglecat 2007-07-20
  • 打赏
  • 举报
回复

事实上,我是以前使用 Data Access Block SqlHelper 的使用,发现了的这个问题

因为 SqlHelper 里面每个 ExecuteXXX 方法都包含了 try-catch-throw,

我发现调用 ExecuteXXX 的方法,顶层捕获异常,堆栈信息只记录到 ExecuteXXX 方法的 throw 那一行,而无法到真正抛出异常的那一行,然后 ExecuteXXX 方法又是调用多个版本的重载方法,结果我就不知道到底是哪个方法,哪行代码错误了,

我只有使用 try{ SqlHelper.ExecuteXXX } catch(SqlException ex) { throw new Exception("db operation failed.", ex); } 将内部异常包装一次

可能,我还是无法描述清楚,回去把我的测试代码上传,供大家测试看看吧

谢谢。
Ivony 2007-07-20
  • 打赏
  • 举报
回复
难不成见鬼了,你把Console的全部代码贴出来。

是.NET Framework 2.0么,说说你的测试环境……
Jinglecat 2007-07-20
  • 打赏
  • 举报
回复
WinForm WebForm Console 都是一样的,我测过了
Ivony 2007-07-20
  • 打赏
  • 举报
回复
你这个到底是WinForm的还是控制台的?

不过我用两个都测试过了,没有发现你说的问题。
Jinglecat 2007-07-20
  • 打赏
  • 举报
回复
这个到的确是奇怪,有没有在catch那里把异常输出看看堆栈是什么?

=========

@Ivony(授人以鱼不如授人以渔,上海谋生)

我打印出来看过了的,ThrowException1() 和 ThrowException2() 用 Console.WriteLine(ex.StackTrace) 总是有差别,

ThrowException1() 无法到原始异常的起点这一行: int i = (int)o; // line 37
Jinglecat 2007-07-20
  • 打赏
  • 举报
回复
@commandosvvv(UnknownData)

事实上,你刚好理解反了


try{}catch(){} 不会再抛出任何异常

try{}catch(throw;){} , try{}catch(Exception)(throw;){} , try{}catch(Exception ex)(throw;){} 是等价的

try{}catch(Exception ex)(throw;){} 与 try{}catch(Exception ex)(throw ex;){}
几乎是相同的,只是后者 CLR 会重新设定异常起点,前者 IL 指令是 rethrow 后者 IL 指令是 throw

这里我要讨论的是,在我的测试场景中,throw; 与 throw ex; 竟然相同了,输出的堆栈信息是一样的。



测试实例:

protected void Button1_Click(object sender, EventArgs e)
{
try {
ThrowException1();
}
catch(Exception ex) {
Response.Write(ex.StackTrace); // 这里只能到 line 40, 我希望的是 line 37
//Console.WriteLine(ex.StackTrace);
//throw;
}

}

protected void Button2_Click(object sender, EventArgs e)
{

try {
ThrowException2();
}
catch(Exception ex) {
Response.Write(ex.StackTrace);
//Console.WriteLine(ex.StackTrace);
//throw;
}
}

private void ThrowException1()
{
try {
object o = null;
int i = (int)o; // line 37
}
catch {
throw; // line 40
}
}
private void ThrowException2()
{
object o = null;
int i = (int)o;
}


Any way, thanks a lot.


Karual 2007-07-19
  • 打赏
  • 举报
回复
太高深了,不明白啊
JF
Jinglecat 2007-07-19
  • 打赏
  • 举报
回复
UUPP
vainnetwork 2007-07-19
  • 打赏
  • 举报
回复
关注,UP
加载更多回复(9)

110,566

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧