【问题标题】:Catch/Modify (Message)/Rethrow Exception of same type捕获/修改(消息)/重新抛出相同类型的异常
【发布时间】:2012-07-25 03:54:11
【问题描述】:

我想要一个从异常中提取信息的中心位置,将我需要的所有信息设置为它的消息参数,然后将该信息作为相同类型的异常重新抛出。

更好的解决方案可能是在最终处理异常(并记录其消息)的地方执行此操作,但是.. 我可以控制抛出异常的地方,而不是接收异常的地方异常,仅记录其 Message 内容。

除了那个设计决定并且考虑到消息是一个只读属性,我会(?)以某种方式创建一个新的异常对象,有没有办法使新的异常对象与原来的类型相同?

这是我的代码,它无法编译 - 它在抛出线(我尝试动态转换对象)上绊倒。

public static void RethrowExceptionWithFullDetailInMessage(string msg, Exception ex)
{
    Exception curEx = ex;
    int cnt = 0;
    while (curEx != null)
    {
        msg += "\r\n";
        msg += cnt++ + " ex.message: " + curEx.Message + "\r\n";
        msg += "Stack: " + curEx.StackTrace;
        curEx = curEx.InnerException;
    }
    object newEx = Convert.ChangeType(new Exception(msg), ex.GetType());
    throw (ex.GetType())newEx;
}

这样做

throw (Exception)newEx;

保留类型? (它编译。)

Convert.ChangeType 是否确保我得到正确类型的异常?

【问题讨论】:

  • 一目了然,您在此处尝试在消息方面执行的操作似乎已通过 Exception.ToString() 方法完成。如果您生成并抛出这样的新异常(您的中断将在此方法中发生,而不是在实际发生异常的地方发生),您将丢失一定数量的上下文并使调试变得更加困难。另外我认为您可能需要一个通用方法来生成正确的类型,但我可能错了......
  • 你有没有用更简单的throw (Exception)newEx 遍历调试器,看看它是否保持类型?
  • Exception.ToString 效果很好。输出需要一点时间来适应,但除此之外,它似乎完全符合我的要求。 (仅用于将来的编码,不适用于手头的问题,或将其分配给消息。)
  • 在这里为所有答案添加评论:这是一个很好的例子,可以为一个糟糕的(设计)问题获得一些真正有见地的答案!至少现在我确定将来如何开始类似的事情。另外,我总是很想了解一些关于反思的知识,我今天确实做到了。非常感谢大家!

标签: c# exception casting throw


【解决方案1】:

您可以这样做来动态调用异常类型的构造函数:

object newEx = Activator.CreateInstance(ex.GetType(), new object[] { msg });

您的原始代码将在运行时失败,因为对于 Convert.ChangeType 工作,异常类型必须实现 IConvertible 并支持转换为其他异常类型,我对此表示怀疑。

【讨论】:

  • 您可能希望它检查(或从Activator.CreateInstance 捕获潜在异常)以确保特定异常类型实际上实现了(string message) 构造函数。我知道很多(如果不是全部在 BCL 中,不确定),但不能保证。
  • 我只需要确保使用 throw (Exception)newEx;,然后一切正常。感谢您的洞察力和想法。
  • 这对我有用,甚至适用于“throw (Exception)newEx;”我的调用代码仍然能够捕获我需要捕获的特定“ArgumentException”。
【解决方案2】:

您在这里尝试做的事情并不像看起来那么容易,并且有很多陷阱需要考虑。

请记住,Convert.ChangeType() 会将一种类型转换为另一种类型(假设存在这样的路径,例如将字符串转换为 int)。大多数例外都不会这样做(他们为什么会这样做?)

为了解决这个问题,您必须在运行时使用 GetType() 方法检查异常类型,并找到具有您可以满足的要求的构造函数并调用它。这里要小心,因为您无法控制所有异常的定义方式,因此无法保证您可以访问“标准”构造函数。

话虽如此,如果你想成为一个规则破坏者,你可以做这样的事情......

void Main()
{
    try
    {   
        throw new Exception("Bar");
    }
    catch(Exception ex)
    {
        //I spit on the rules and change the message anyway
        ex.GetType().GetField("_message", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(ex, "Foo");
        throw ex;
    }
}

【讨论】:

  • 这是一个不错的肮脏黑客解决方案。也感谢对​​转换的洞察力。我认为 _message 是属性 Message 的基础字段?!无论如何,尝试过,它正在工作。
  • 你是对的,_message 是 Message 属性的支持字段
  • 这对我有用,并且完全符合我的要求,但它确实让“Resharper”感到不安,出现“可能的 System.NullReferenceException”和“可能预期的异常重新抛出”警告..​​....
【解决方案3】:

可能有点晚了,但这对你有用吗?

catch (Exception ex)
{
    throw new Exception("New message", ex);
}

【讨论】:

  • 这种方法的问题是,如果你捕获了一个特定的异常,比如“System.ArgumentException”,你就会重新抛出一个“System.Exception”类型的一般异常。如果您在调用链的顶部有代码期望捕获它不会捕获的特定异常。我自己的代码写得像你的例子,我只是犯了这个错误!
【解决方案4】:

您可以像这样通过反射更改异常消息...

Exception exception = new Exception("Some message.");
var type = typeof(Exception);
var flags = BindingFlags.Instance | BindingFlags.NonPublic;
var fieldInfo = type.GetField("_message", flags);
fieldInfo.SetValue(exception, message);

所以你可以创建一个扩展方法...

namespace ExceptionExample
{
    public static class ExceptionExtensions
    {
        public static void SetMessage(this Exception exception, string message)
        {
            if (exception == null)
                throw new ArgumentNullException(nameof(exception));

            var type = typeof(Exception);
            var flags = BindingFlags.Instance | BindingFlags.NonPublic;
            var fieldInfo = type.GetField("_message", flags);
            fieldInfo.SetValue(exception, message);
        }
    }
}

然后使用它...

...
using static ExceptionExample.ExceptionExtensions;

public class SomeClass
{
    public void SomeMethod()
    {
        var reader = AnotherClass.GetReader();
        try
        {
            reader.Read();
        }
        catch (Exception ex)
        {
            var connection = reader?.Connection;
            ex.SetMessage($"The exception message was replaced.\n\nOriginal message: {ex.Message}\n\nDatabase: {connection?.Database}");
            throw; // you will not lose the stack trace
        }
    }
}

您必须记住,如果您使用“throw ex;”堆栈跟踪将丢失

为避免这种情况您必须使用“throw;”无一例外

【讨论】:

    【解决方案5】:

    补充评论。

    这些都可以补充异常消息,但我发现使用“throw”并没有保留 StackTrace - 最后一个跟踪指向实际的“throw”语句(删除根本原因位置)。

    从其他地方的讨论来看,很明显,由于 CLR 堆栈限制,某些情况下 throw 无法保留

    Throw and preserve stack trace not as expected as described by Code Analysis

    解决方案:在每个异常中转储 StackTrace(例如并添加到错误消息中)和/或转储到日志记录

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-11-30
      • 1970-01-01
      • 2011-06-27
      • 1970-01-01
      • 1970-01-01
      • 2018-12-17
      • 2013-04-14
      相关资源
      最近更新 更多