C# 两个异常管理问题

C# 两个异常管理问题,c#,exception,exception-handling,C#,Exception,Exception Handling,我有几个关于网站异常管理的问题: 在catch块中,我可以有一个带有句柄异常方法的静态类,该方法可以像这样处理异常: catch (Exception ex) { throw ExceptionHandler.HandleException(...); } 其中ExceptionHandler.HandleException是一个静态方法,返回System.Exception类型的变量。这是一种好的做法吗?这种方法有任何可能的问题吗?它是线程安全的吗 在我

我有几个关于网站异常管理的问题:

在catch块中,我可以有一个带有句柄异常方法的静态类,该方法可以像这样处理异常:

   catch (Exception ex)
    {
        throw ExceptionHandler.HandleException(...);
    }
其中ExceptionHandler.HandleException是一个静态方法,返回System.Exception类型的变量。这是一种好的做法吗?这种方法有任何可能的问题吗?它是线程安全的吗

在我的应用程序中,我有一个DAL层,由业务层调用,业务层由UI调用。那么,重新抛出所有自定义异常是否是一种好的做法,这样它们就会出现在UI上,在UI上显示,而System.Exception类型会被记录,我会在catch块中抛出一个自定义异常? 对于DAL中的eg和业务层,如:

    catch (CustomExceptionBase ex)
    {
        throw;
    }
    catch (Exception sysEx)
    {
        ICustomExceptionBase ex = new SysException(sysEx);
        ex.Handle();
        throw BusinessException("Some problem while serving your request");
    }
在UI层中,就像这样

    catch (CustomExceptionBase ex)
    {
        //when custom exception bubbles up; code to display text to user on screen
    }
    catch (Exception sysEx)
    {
        ICustomExceptionBase ex = new SysException(sysEx);
        ex.Handle();
        //display error on screen;
    }
这里CustomExceptionBase实现ICustomeExceptionBase并继承Exception。SysException和BusinessException都继承自CustomExceptionBase

谢谢你的时间

编辑
在system.Exceptions块中重新刷新的目的是,如果出现致命错误,如数据库连接丢失或类似错误,我会将其记录到技术帮助台,并返回一个票证号,然后重新刷新,以便用户知道出现了问题,这是您要跟进的参考号。对于DAL层或业务层中的所有自定义异常,我只是将其一直冒泡到显示文本的UI。

我怀疑一些答案至少完全取决于您的体系结构。在第一种情况下,这完全取决于ExceptionHandler.HandleException的确切功能。它是基于某些条件生成新异常,还是只返回原始异常

它的线程安全与否完全取决于它的实现。例如,在以下简单的情况下,我会说它是线程安全的:

public static Exception ExceptionHandler.HandleException(Exception ex)
{
    return ex;
}
在其他情况下,它可能很容易不是线程安全的。例如:

public static string message;
public static Exception ExceptionHandler.HandleException(Exception ex)
{
    message = ex.ToString;
    sleep(2000);
    return new Exception(message);
}
后一个示例显然可以在另一个线程休眠时更改消息变量

至于第二个。。。应该在处理异常有意义的地方处理异常。没有硬性规定。如果代码的某些部分可以从异常中恢复,或者愿意跳过异常,那么就在该点捕获它,而不是更早。如果一个异常真的是致命的,那么就不应该试图捕捉它并假装它不是这样,所以你应该让它一直冒泡到顶部,然后做一些事情,比如提醒你的用户事情已经崩溃,你需要重新启动或者其他什么


因此,这实际上取决于自定义异常的含义。如果它们只是表示您想重试此操作,那么这与表示数据完整性已受损的异常不同:0==1。这两个问题可能都是自定义的,因此实际上是由您决定在何处处理问题。

我怀疑至少有一些答案完全取决于您的体系结构。在第一种情况下,这完全取决于ExceptionHandler.HandleException的确切功能。它是基于某些条件生成新异常,还是只返回原始异常

它的线程安全与否完全取决于它的实现。例如,在以下简单的情况下,我会说它是线程安全的:

public static Exception ExceptionHandler.HandleException(Exception ex)
{
    return ex;
}
在其他情况下,它可能很容易不是线程安全的。例如:

public static string message;
public static Exception ExceptionHandler.HandleException(Exception ex)
{
    message = ex.ToString;
    sleep(2000);
    return new Exception(message);
}
后一个示例显然可以在另一个线程休眠时更改消息变量

至于第二个。。。应该在处理异常有意义的地方处理异常。没有硬性规定。如果代码的某些部分可以从异常中恢复,或者愿意跳过异常,那么就在该点捕获它,而不是更早。如果一个异常真的是致命的,那么就不应该试图捕捉它并假装它不是这样,所以你应该让它一直冒泡到顶部,然后做一些事情,比如提醒你的用户事情已经崩溃,你需要重新启动或者其他什么


因此,这实际上取决于自定义异常的含义。如果它们只是表示您想重试此操作,那么这与表示数据完整性已受损的异常不同:0==1。这两个可能都是自定义的,因此实际上是由您决定在何处处理事情。

是的,您可以在catch块内调用静态异常处理程序,只要不引用任何静态变量,它就可能是线程安全的


你应该看看微软的企业图书馆。它具有几乎相同的设计,但使用web.config中定义的异常策略来控制异常的冒泡、包装或丢弃方式。将其与应用程序日志块结合,您就有了一个完整的解决方案。

是的,您可以在catch块内调用静态异常处理程序,只要不引用任何静态变量,它就可能是线程安全的

你应该看看麦克
rosoft的企业图书馆。它具有几乎相同的设计,但使用web.config中定义的异常策略来控制异常的冒泡、包装或丢弃方式。与应用程序日志块进行耦合,您有一个完整的解决方案。

< P>首先我们需要考虑异常类型,在任何业务异常中可以是业务异常或技术/系统异常。 在BusinessException中,我们可以发送带有错误的自定义异常 细节

在技术/系统异常情况下,我们不应该处理它,而是让它发生 弹出到UI层。UI可以决定应在中显示的错误 例外情况

在您的方法中,如果您处理异常或抛出自定义 异常:调用跟踪丢失


首先我们需要考虑异常类型,在任何业务异常中可以是业务异常或技术/系统异常。 在BusinessException中,我们可以发送带有错误的自定义异常 细节

在技术/系统异常情况下,我们不应该处理它,而是让它发生 弹出到UI层。UI可以决定应在中显示的错误 例外情况

在您的方法中,如果您处理异常或抛出自定义 异常:调用跟踪丢失


就其本身而言,使用静态方法来处理异常/重试异常并没有任何技术问题,但是从最佳实践的角度来看,使用单一方法神奇地处理异常给我留下了潜在的代码味道。例外就是例外,每个个案都需要仔细考虑,以确保您所做的事情是正确的,因此我发现您的HandleException方法不太可能总是做一些明智的事情

作为一个极端的例子,我知道有一个这样的应用程序,其中几乎每一个方法都被包装在一个try-catch块中,并调用一个静态异常处理程序方法,该方法抛出了一个通用的MyApplicationException类型。这是一个非常糟糕的想法:

它使代码混乱 这使得很难理解堆栈跟踪 这使得调用方很难捕获和处理特定的异常类型 这使得抛出异常的性能损失比以前更大 我最喜欢的是一个没有实现的方法,它看起来有点像这样:

void SomeException()
{
    try
    {
        throw new NotImplementedException();
    }
    catch(Exception ex)
    {
        throw ExceptionHandler.HandleException(...);
    }
}
当然,最糟糕的一点是它完全没有头脑。正如我之前所说,例外情况是例外的-每次尝试。。。catch块需要仔细考虑它的行为,使用泛型HandleException方法是一个立即的警告标志,表明情况可能并非如此

转述例外 一般来说,您只应在两种情况下重新显示异常:

当您要向异常添加上下文信息时,例如正在处理的当前文件的名称 当您必须捕获异常以处理某些特定情况时,例如

冒泡,即捕获和重新冒泡一个异常而不做任何事情是完全不必要的-这是异常本身已经设计好要做的事情

异常处理 其他人说应该在有意义的地方处理例外情况,我甚至自己给出了这个建议,但事后看来,我认为这不是特别有用的建议

人们通常的意思是,您应该出于特定的原因处理异常,并且您应该根据该原因选择在应用程序中处理该异常的位置

例如,如果您希望显示一条错误消息,通知用户如果您遇到拒绝访问错误,他们没有修改文件的权限,那么您的UI代码中可能有一个特定的try-catch块来执行此操作:

catch (IOException ex)
{
    long win32ErrorCode = Marshal.GetHRForException(ex) & 0xFFFF;
    if (win32ErrorCode == ERROR_ACCESS_DENIED)
    {
        // Display "access denied error"
    }
    else
    {
        throw;
    }
}
请注意,这是我们希望处理的一个非常特殊的情况-它只捕获我们感兴趣的特定异常类型,并执行附加检查以过滤到我们感兴趣的特定情况

或者,如果您希望记录未处理的错误或向用户优雅地显示错误消息,而不是IIS 505错误屏幕,则可以在Global.asax中或通过自定义错误页执行此操作-

我的观点是,在处理异常时,我们正在仔细考虑我们希望在应用程序功能方面实现什么,例如重试逻辑、错误消息、日志记录等。。。并实现我们的异常处理逻辑,以最有针对性的方式具体解决这些需求——没有神奇的异常处理框架,也没有样板代码

尽可能避免异常! 我通常发现最好的策略就是尽可能地完全避免异常!例如,如果您的页面解析用户输入的数字,并且您希望在用户输入愚蠢的值时显示验证消息,那么请提前验证您的输入 捕获异常的广告:

坏的:

好:

用户总是输入无效数据-处理这实际上是应用程序逻辑,不应该属于真正的异常处理-这当然不是我称之为异常的事件


当然,这并不总是可能的,但是我发现使用这种策略可以简化代码并使遵循应用程序逻辑变得更容易。

使用静态方法来处理异常/重试异常本身并没有任何技术问题,然而,从最佳实践的角度来看,拥有一个神奇地处理异常的单一方法给我的印象是潜在的代码气味。例外就是例外,每个个案都需要仔细考虑,以确保您所做的事情是正确的,因此我发现您的HandleException方法不太可能总是做一些明智的事情

作为一个极端的例子,我知道有一个这样的应用程序,其中几乎每一个方法都被包装在一个try-catch块中,并调用一个静态异常处理程序方法,该方法抛出了一个通用的MyApplicationException类型。这是一个非常糟糕的想法:

它使代码混乱 这使得很难理解堆栈跟踪 这使得调用方很难捕获和处理特定的异常类型 这使得抛出异常的性能损失比以前更大 我最喜欢的是一个没有实现的方法,它看起来有点像这样:

void SomeException()
{
    try
    {
        throw new NotImplementedException();
    }
    catch(Exception ex)
    {
        throw ExceptionHandler.HandleException(...);
    }
}
当然,最糟糕的一点是它完全没有头脑。正如我之前所说,例外情况是例外的-每次尝试。。。catch块需要仔细考虑它的行为,使用泛型HandleException方法是一个立即的警告标志,表明情况可能并非如此

转述例外 一般来说,您只应在两种情况下重新显示异常:

当您要向异常添加上下文信息时,例如正在处理的当前文件的名称 当您必须捕获异常以处理某些特定情况时,例如

冒泡,即捕获和重新冒泡一个异常而不做任何事情是完全不必要的-这是异常本身已经设计好要做的事情

异常处理 其他人说应该在有意义的地方处理例外情况,我甚至自己给出了这个建议,但事后看来,我认为这不是特别有用的建议

人们通常的意思是,您应该出于特定的原因处理异常,并且您应该根据该原因选择在应用程序中处理该异常的位置

例如,如果您希望显示一条错误消息,通知用户如果您遇到拒绝访问错误,他们没有修改文件的权限,那么您的UI代码中可能有一个特定的try-catch块来执行此操作:

catch (IOException ex)
{
    long win32ErrorCode = Marshal.GetHRForException(ex) & 0xFFFF;
    if (win32ErrorCode == ERROR_ACCESS_DENIED)
    {
        // Display "access denied error"
    }
    else
    {
        throw;
    }
}
请注意,这是我们希望处理的一个非常特殊的情况-它只捕获我们感兴趣的特定异常类型,并执行附加检查以过滤到我们感兴趣的特定情况

或者,如果您希望记录未处理的错误或向用户优雅地显示错误消息,而不是IIS 505错误屏幕,则可以在Global.asax中或通过自定义错误页执行此操作-

我的观点是,在处理异常时,我们正在仔细考虑我们希望在应用程序功能方面实现什么,例如重试逻辑、错误消息、日志记录等。。。并实现我们的异常处理逻辑,以最有针对性的方式具体解决这些需求——没有神奇的异常处理框架,也没有样板代码

尽可能避免异常! 我通常发现最好的策略就是尽可能地完全避免异常!例如,如果您的页面解析用户输入的数字,并且您希望在用户输入愚蠢的值时显示验证消息,那么请提前验证您的输入,而不是捕获异常:

坏的:

好:

用户总是输入无效数据-处理这实际上是应用程序逻辑,不应该属于真正的异常处理-这当然不是我称之为异常的事件


当然,这并不总是可能的,但是我发现使用这种策略可以简化代码并使遵循应用程序逻辑变得更容易。

Heh,如果使其成为线程安全的,那么它将是线程安全的。除此之外,我要说的是,如果您已经处理了一个异常,就不需要重新抛出它。如果您所做的只是记录异常并重新抛出它,那么catch块可能毫无意义。在我看来,处理异常意味着以一种不需要重新抛出的方式来处理它。但在这里,系统异常的目的是1。日志i

这就是前把手所能做的。提醒用户出了问题,因此抛出..可能Handle是一个误导性的名称..我认为处理所有可能的异常是一种不好的做法,除了一些警告用户并关闭应用程序的顶级代码。只处理您知道如何处理的异常。我希望在不向用户显示asp.net黄色错误屏幕的情况下优雅地处理所有错误:嘿,如果您使其成为线程安全的,它将是线程安全的。除此之外,我要说的是,如果您已经处理了一个异常,就不需要重新抛出它。如果您所做的只是记录异常并重新抛出它,那么catch块可能毫无意义。在我看来,处理异常意味着以一种不需要重新抛出的方式来处理它。但在这里,系统异常的目的是1。记录它,这是所有的前处理会做2。提醒用户出了问题,因此抛出..可能Handle是一个误导性的名称..我认为处理所有可能的异常是一种不好的做法,除了一些警告用户并关闭应用程序的顶级代码。仅处理您知道如何处理的异常。我希望在不向用户显示asp.net黄色错误屏幕的情况下优雅地处理所有错误:在第一种情况下,Handle调用log4net来记录异常。在第二个示例中,如果出现致命错误,如数据库连接丢失或类似错误,我会将其记录到技术帮助台,并返回一个票证号码,然后重新发送,以便用户知道出现了错误,这是您要跟进的参考号。然后,您的第一个问题将成为log4net线程安全。我自己从来没用过,但我想可能是的。您可能也不想抛出从方法返回的内容,而只是抛出,以便原始异常与原始上下文一起传递。第二,这听起来是一个明智的策略。对于这样的事情,很难给出一个明确的答案,因为这完全取决于个别情况。在第一种情况下,句柄是调用log4net来记录一个异常。在第二个示例中,如果出现致命错误,如数据库连接丢失或类似错误,我会将其记录到技术帮助台,并返回一个票证号码,然后重新发送,以便用户知道出现了错误,这是您要跟进的参考号。然后,您的第一个问题将成为log4net线程安全。我自己从来没用过,但我想可能是的。您可能也不想抛出从方法返回的内容,而只是抛出,以便原始异常与原始上下文一起传递。第二,这听起来是一个明智的策略。对于这样的问题很难给出一个明确的答案,因为这完全取决于个别情况。在自定义异常中,唯一需要设置的是编码器发送给类以获取设置的错误消息属性。如果业务层在用户输入中遇到一些异常,它会将其标记为:throw businessException由于您的文档没有正确的信息,您的应用程序被拒绝;此时,业务层需要将一些文档发送到另一个服务进行处理,而该服务返回一个如上所述的错误。在这里,它会一直冒泡到顶部。IT服务台不会对调试此异常感兴趣当服务停止时,我想告诉用户。。。嘿,有什么地方出了问题。我们已经记录了,这是您的车票号码,如果您想稍后再与我们联系的话。。因此,在这种情况下,ex.Handle会将错误记录到文本文件或事件日志中,然后抛出一个附加了票证号的业务异常,这样用户就可以使用该信息进入网站的错误页面。在这种情况下,您可以使用Microsoft.ENterpriseLibrary或log4net记录错误详细信息。您可以将业务异常抛出到UI层并显示相应的消息。\n在自定义异常中,唯一要设置的是编码器发送给类以进行设置的错误消息属性。如果业务层在用户输入中遇到一些异常,它会将其标记为:throw businessException由于您的文档没有正确的信息,您的应用程序被拒绝;此时,业务层需要将一些文档发送到另一个服务进行处理,而该服务返回一个如上所述的错误。在这里,它会一直冒泡到顶部。IT服务台不会对调试此异常感兴趣当服务停止时,我想告诉用户。。。嘿,有什么地方出了问题。我们已经记录了,这是您的车票号码,如果您想稍后再与我们联系的话。。因此,在这种情况下,ex.Handle将把错误记录到文本文件或事件日志中,然后抛出一个附加了票证号的业务异常,这样用户就可以使用该信息进入网站的错误页面
n记录错误详细信息您可以使用Microsoft.ENterpriseLibrary或log4net。您可以向UI层抛出一个业务异常并显示相应的消息。\n感谢Justin花时间给我提供了一个非常详细的答案。当我消化您所输入的所有内容时,我脑海中浮现出一些问题:我不打算为可以在UI上验证的数据抛出异常。在我的asp.net MVC应用程序中,Data accesslayer处理某个文档时返回一个错误代码,该错误代码被解释为:文档中没有足够的详细信息,然后我在DAL中抛出一个自定义异常,该异常会一直冒泡到UI层。IT帮助台不会对调试此异常感兴趣,因此我不会记录它。但是,如果它是一个错误,如数据库服务器关闭,则会在System.exception catch块中被捕获。现在我做两件事:用[ex.Handle]将错误记录到文本文件或事件日志中,然后抛出一个附加了票证号或类似引用的自定义异常,这样用户就可以带着该信息进入网站的错误页面。dal层中抛出的此异常在业务层中被重新抛出,在该层中,它现在可以在要显示的UI中使用..您认为使用抛出来冒泡异常是错误的做法吗,正确的方法是,每个方法返回一个包含ErrorString属性的对象?我在某个地方读到过,抛出异常不会造成任何可测量的性能影响,因此我认为使用抛出新的CustomException来冒泡异常,而不是创建一个额外的对象来将错误字符串传递到UI层没有什么害处。您还有什么建议?@user20358在您的情况下,并行的方法是重构您的API,以便在不使用异常的情况下报告文档案例中的不够详细信息,例如,让它返回一个任务状态对象,封装处理未完成的事实和指示原因的消息。这很可能是因为太麻烦而得不到什么好处,但是如果这个异常从未被记录,那么抛出这个异常时,它听起来非常像它的业务。然后处理其他异常,例如全球缺少数据库连接。感谢Justin抽出时间为我提供了非常详细的答案。当我消化您所输入的所有内容时,我脑海中浮现出一些问题:我不打算为可以在UI上验证的数据抛出异常。在我的asp.net MVC应用程序中,Data accesslayer处理某个文档时返回一个错误代码,该错误代码被解释为:文档中没有足够的详细信息,然后我在DAL中抛出一个自定义异常,该异常会一直冒泡到UI层。IT帮助台不会对调试此异常感兴趣,因此我不会记录它。但是,如果它是一个错误,如数据库服务器关闭,则会在System.exception catch块中被捕获。现在我做两件事:用[ex.Handle]将错误记录到文本文件或事件日志中,然后抛出一个附加了票证号或类似引用的自定义异常,这样用户就可以带着该信息进入网站的错误页面。dal层中抛出的此异常在业务层中被重新抛出,在该层中,它现在可以在要显示的UI中使用..您认为使用抛出来冒泡异常是错误的做法吗,正确的方法是,每个方法返回一个包含ErrorString属性的对象?我在某个地方读到过,抛出异常不会造成任何可测量的性能影响,因此我认为使用抛出新的CustomException来冒泡异常,而不是创建一个额外的对象来将错误字符串传递到UI层没有什么害处。您还有什么建议?@user20358在您的情况下,并行的方法是重构您的API,以便在不使用异常的情况下报告文档案例中的不够详细信息,例如,让它返回一个任务状态对象,封装处理未完成的事实和指示原因的消息。这很可能是因为太麻烦而得不到什么好处,但是如果这个异常从未被记录,那么抛出这个异常时,它听起来非常像它的业务。然后处理其他异常,例如全局缺少数据库连接。