C# 传递异常的正确方法是什么?(C#)

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2210841/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-07 00:28:11  来源:igfitidea点击:

What is the right way to pass on an exception? (C#)

c#exception

提问by avesse

I'm wondering what the correct way is to pass on an exception from one method to another.

我想知道将异常从一种方法传递到另一种方法的正确方法是什么。

I'm working on a project that is divided into Presentation (web), Business and Logic layers, and errors (e.g. SqlExceptions) need to be passed down the chain to notify the web layer when something goes wrong.

我正在做一个项目,它分为展示(web)、业务和逻辑层,错误(例如 SqlExceptions)需要沿着链向下传递以在出现问题时通知 web 层。

I've seen 3 basic approaches:

我见过 3 种基本方法:

try  
{  
    //error code
} 
catch (Exception ex)
{
    throw ex;
}

(simply rethrow)

(简单地重新抛出)

try  
{  
    //error code
} 
catch (Exception ex)
{
    throw new MyCustomException();
}

(throw a custom exception, so that a dependency on the data provider is not passed on)
and then simply

(抛出自定义异常,以便不传递对数据提供者的依赖)
然后简单地

//error code

(not doing anything at all, letting the error bubble up by itself)

(根本不做任何事情,让错误自己冒泡)

Naturally there's some logging happening in the catch block too.

当然,catch 块中也会发生一些日志记录。

I prefer number 3, while my colleague uses method 1, but neither of us can really motivate why.

我更喜欢第 3 种方法,而我的同事使用方法 1,但我们都无法真正激发出原因。

What are the advantages/disadvantages of using each method? Is there a better method I don't know of? Is there an accepted Best Way?

使用每种方法的优点/缺点是什么?有没有更好的方法我不知道?是否有公认的最佳方式?

采纳答案by Patrick Desjardins

If you do nothing you should simply let it go upper where some one will handle it.

如果你什么都不做,你应该简单地让它上升到有人会处理它的地方。

You can always handle a part of it (like logging) and re-throw it. You can re-throw by simply sending throw;without having to explicit the ex name.

你总是可以处理它的一部分(比如日志记录)并重新抛出它。您可以通过简单地发送来重新抛出,throw;而不必显式地显示 ex 名称。

try
{

}
catch (Exception e)
{
    throw;
}

The advantage to handle it is that you can ensure that some mechanism is there to notify you that you have an error where you do not suspect to have one.

处理它的好处是您可以确保有某种机制来通知您有错误,而您不怀疑有错误。

But, in some case, let say a Third Party, you want to let the user handle it and when it's that case you should let it continue to bubble up.

但是,在某些情况下,假设是第三方,您希望让用户处理它,在这种情况下,您应该让它继续冒泡。

回答by Taylor Leese

The correct way to re-throw an exception in C# is like below:

在 C# 中重新抛出异常的正确方法如下:

try
{
    ....
}
catch (Exception e)
{
    throw;
}

See this threadfor specifics.

有关具体信息,请参阅此线程

回答by Nick Larsen

Only use try/catch blocks around exceptions you expect and can handle. If you catch something you cannot handle, it defeats the purpose of try/catch which is to handle expected errors.

仅在您期望并可以处理的异常周围使用 try/catch 块。如果你捕捉到一些你无法处理的东西,它就会违背 try/catch 处理预期错误的目的。

Catching big exception is rarely a good idea. The first time you catch an OutOfMemoryException are you really going to be able to gracefully handle it? Most API's document the exceptions each method can throw, and those should be the only ones handled and only if you can gracefully handle it.

捕捉大异常很少是一个好主意。第一次捕获 OutOfMemoryException 时,您真的能够优雅地处理它吗?大多数 API 都记录了每个方法可以抛出的异常,这些应该是唯一处理的异常,并且只有在您可以优雅地处理它的情况下。

If you want to handle the error further up the chain, let it bubble up on its own without catching and rethrowing it. The only exception to this would be for logging purposes, but logging at every step is going to do an excessive amount of work. It is better to just document where exceptions your public methods can be expected to allow to bubble up and let the consumer of your API make the decision on how to handle it.

如果你想在链上进一步处理错误,让它自己冒泡,不要捕捉并重新抛出它。唯一的例外是用于记录目的,但在每一步进行记录都会做大量的工作。最好只记录公共方法可能允许出现的异常,并让 API 的使用者决定如何处理它。

回答by JaredPar

I think you should start with a slightly different question

我认为你应该从一个稍微不同的问题开始

How do I expect other components to interact with exceptions thrown from my module?

我如何期望其他组件与我的模块抛出的异常进行交互?

If the consumers are quite capable of handling the exceptions thrown by the lower / data layers then quite simply do nothing. The upper layer is capable of handling the exceptions and you should only do the minimum amount necessary to maintain your state and then rethrow.

如果消费者完全有能力处理低层/数据层抛出的异常,那么什么都不做。上层能够处理异常,您应该只做维持状态所需的最少数量,然后重新抛出。

If the consumers cannot handle low level exceptions but instead need a bit higher level exceptions, then create a new exception class which they can handle. But make sure to pass on the original exception a the inner exception.

如果消费者无法处理低级异常,而是需要更高级别的异常,则创建一个他们可以处理的新异常类。但请确保将原始异常传递给内部异常。

throw new MyCustomException(msg, ex);

回答by dh.

Normally you'd catch only exceptions which you expect, can handle and let application work in a normal way further. In case you'd like to have some additional error logging you'll catch an exception, do logging and rethrow it using "throw;" so the stack trace is not modified. Custom exceptions are usually created for the purpose of reporting your application specific errors.

通常,您只会捕获您期望的异常,可以处理并让应用程序进一步以正常方式工作。如果你想记录一些额外的错误日志,你会捕捉到一个异常,做日志记录并使用“throw;”重新抛出它。所以堆栈跟踪没有被修改。自定义异常通常是为了报告您的应用程序特定错误而创建的。

回答by Daniel Earwicker

I've seen (and held) various strong opinions about this. The answer is that I don't think there currently is an ideal approach in C#.

我已经看到(并持有)对此的各种强烈意见。答案是我认为目前在 C# 中没有理想的方法。

At one point I felt that (in a Java-minded way) the exception is part of the binary interface of a method, as much as the return type and parameter types. But in C#, it simply isn't. This is clear from the fact that there is no throws specification system.

有一次我觉得(以 Java 思想的方式)异常是方法的二进制接口的一部分,就像返回类型和参数类型一样。但在 C# 中,它根本不是。从没有抛出规范系统的事实可以清楚地看出这一点。

In other words, you can if you wish take the attitude that only your exception types should fly out of your library's methods, so your clients don't depend on your library's internal details. But few libraries bother to do this.

换句话说,如果你愿意,你可以采取这样的态度,即只有你的异常类型应该飞出你的图书馆的方法,这样你的客户就不会依赖你图书馆的内部细节。但是很少有图书馆费心去这样做。

The official C# team advice is to catch each specific type that might be thrown by a method, if you think you can handle them. Don't catch anything you can't really handle. This implies no encapsulation of internal exceptions at library boundaries.

官方的 C# 团队建议是捕获方法可能抛出的每个特定类型,如果您认为可以处理它们。不要抓住任何你不能真正处理的东西。这意味着没有在库边界处封装内部异常。

But in turn, that means that you need perfect documentation of what might be thrown by a given method. Modern applications rely on mounds of third party libraries, rapidly evolving. It makes a mockery of having a static typing system if they are all trying to catch specific exception types that might not be correct in future combinations of library versions, with no compile-time checking.

但反过来,这意味着您需要完美记录给定方法可能抛出的内容。现代应用程序依赖于快速发展的大量第三方库。如果他们都试图捕获在未来的库版本组合中可能不正确的特定异常类型,而没有编译时检查,那么它就会嘲笑拥有静态类型系统。

So people do this:

所以人们这样做:

try
{
}
catch (Exception x) 
{
    // log the message, the stack trace, whatever
}

The problem is that this catches all exception types, including those that fundamentally indicate a severe problem, such as a null reference exception. This means the program is in an unknown state. The moment that is detected, it ought to shut down before it does some damage to the user's persistent data (starts trashing files, database records, etc).

问题是这会捕获所有异常类型,包括那些从根本上表明存在严重问题的异常类型,例如空引用异常。这意味着程序处于未知状态。检测到的那一刻,它应该在对用户的持久数据造成一些损害(开始破坏文件、数据库记录等)之前关闭。

The hidden problem here is try/finally. It's a great language feature - indeed it's essential - but if a serious enough exception is flying up the stack, should it really be causing finally blocks to run? Do you really want the evidence to be destroyed when there's a bug in progress? And if the program is in an unknown state, anythingimportant could be destroyed by those finally blocks.

这里隐藏的问题是 try/finally。这是一个很棒的语言特性——实际上它是必不可少的——但是如果一个足够严重的异常正在堆栈上飞,它真的会导致 finally 块运行吗?您真的希望在发生错误时销毁证据吗?如果程序处于未知状态,任何重要的东西都可能被那些 finally 块破坏。

So what you really want is (updated for C# 6!):

所以你真正想要的是(针对 C# 6 更新!):

try
{
    // attempt some operation
}
catch (Exception x) when (x.IsTolerable())
{
    // log and skip this operation, keep running
}

In this example, you would write IsTolerableas an extension method on Exceptionwhich returns falseif the innermost exception is NullReferenceException, IndexOutOfRangeException, InvalidCastExceptionor any other exception types that youhave decided must indicate a low-level error that must halt execution and require investigation. These are "intolerable" situations.

在这个例子中,你会写IsTolerable为扩展方法的Exception返回false如果最里面的例外是NullReferenceExceptionIndexOutOfRangeExceptionInvalidCastException或任何其他异常类型是已经决定必须指明一个低级的错误必须中止执行,并要求调查。这些都是“不能容忍”的情况。

This might be termed "optimistic" exception handling: assuming that all exceptions are tolerable except for a set of known blacklisted types. The alternative (supported by C# 5 and earlier) is the "pessimistic" approach, where only known whitelisted exceptions are considered tolerable and anything else is unhandled.

这可能被称为“乐观”异常处理:假设除了一组已知的列入黑名单的类型之外,所有异常都是可以容忍的。替代方案(受 C# 5 及更早版本支持)是“悲观”方法,其中仅将已知的列入白名单的异常视为可容忍的,而其他任何异常均未处理。

Years ago the pessimistic approach was the official recommended stance. But these days the CLR itself catches all exceptions in Task.Runso it can move errors between threads. This causes finally blocks to execute. So the CRL is veryoptimistic by default.

多年前,悲观的态度是官方推荐的立场。但是现在,CLR 本身会捕获所有异常,Task.Run因此它可以在线程之间移动错误。这会导致 finally 块执行。所以默认情况下,CRL 是非常乐观的。

You can also enlist with the AppDomain.UnhandledExceptionevent, save as much information as you can for support purposes (at least the stack trace) and then call Environment.FailFastto shut down your process before any finallyblocks can execute (which might destroy valuable information needed to investigate the bug, or throw other exceptions that hide the original one).

您还可以使用AppDomain.UnhandledException事件,为支持目的保存尽可能多的信息(至少是堆栈跟踪),然后调用Environment.FailFast在任何finally块可以执行之前关闭您的进程(这可能会破坏有价值的信息需要调查错误,或抛出其他隐藏原始异常的异常)。

回答by John Knoeller

I'm not sure that there really is an accepted best practices, but IMO

我不确定是否真的有公认的最佳实践,但 IMO

try  // form 1: useful only for the logging, and only in debug builds.
{  
    //error code
} 
catch (Exception ex)
{
    throw;// ex;
}

Makes no real sense except for the logging aspect, so I would do this only in a debug build. Catching an rethrowing is costly, so you should have a reason for paying that cost other than just that you like looking at the code.

除了日志记录方面没有任何实际意义,因此我只会在调试版本中执行此操作。捕获重新抛出是昂贵的,因此您应该有理由支付这笔费用,而不仅仅是您喜欢查看代码。

try  // form 2: not useful at all
{  
    //error code
} 
catch (Exception ex)
{
    throw new MyCustomException();
}

This one makes no sense at all. It's discardingthe real exception and replacing it with one that contains less information about the actual real problem. I can see possibly doing this if I want to augment the exception with some information about what was happening.

这一点完全没有意义。它丢弃了真正的异常,并用包含较少有关实际实际问题的信息的异常来替换它。如果我想用一些关于正在发生的事情的信息来增加异常,我可以看到可能会这样做。

try  // form 3: about as useful as form 1
{  
    //error code
} 
catch (Exception ex)
{
    throw new MyCustomException(ex, MyContextInformation);
}

But I would say in nearly all cases where you are not handlingthe exception the best form is to simply let a higher level handler deal with it.

但我会说,在几乎所有不处理异常的情况下,最好的形式是简单地让更高级别的处理程序处理它。

// form 4: the best form unless you need to log the exceptions.
// error code. no try - let it percolate up to a handler that does something productive.

回答by Eric Lippert

No one has yet pointed out the very first thing you should be thinking of: what are the threats?

还没有人指出你应该考虑的第一件事:威胁是什么?

When a back-end tier throws an exception, something terrible and unexpected has happened. The unexpected terrible situation might have happened because that tier is under attack by a hostile user. The lastthing you want to do in that case is serve up to the attacker a detailed list of everything that went wrong and why. When something goes wrong on the business logic tier the right thing to do is to carefully log all information about the exception and replace the exception with a generic "We're sorry, something went wrong, administration has been alerted, please try again" page.

当后端层抛出异常时,就会发生一些可怕且意想不到的事情。出乎意料的可怕情况可能发生了,因为该层正受到敌对用户的攻击。在最后你想在这种情况下,要做的事情是服务到攻击者出了什么问题,为什么一切的详细清单。当业务逻辑层出现问题时,正确的做法是仔细记录有关异常的所有信息,并将异常替换为通用的“很抱歉,出了点问题,管理已收到警报,请重试”页面.

One of the things to keep track of is all the information you have about the user and what they were doing when the exception happened. That way if you detect that the same user always seems to be causing problems, you can evaluate whether they are likely to be probing you for weaknesses, or simply using an unusual corner of the application that wasn't sufficiently tested.

要跟踪的一件事是您拥有的有关用户的所有信息以及异常发生时他们正在做什么。这样,如果您发现同一个用户似乎总是在制造问题,您可以评估他们是否可能会探查您的弱点,或者只是使用未充分测试的应用程序的一个不寻常的角落。

Get the security design right first, and only then worry about diagnosis and debugging.

获得安全设计权第一,然后才是担心的诊断和调试。