我想要一个中心位置从异常中提取信息,将我需要的所有信息设置为其message参数,然后将该信息重新抛出为相同类型的异常。
更好的解决方案可能是在最终处理异常的地方(并记录其消息)执行此操作,但是..我可以控制抛出异常的地方,而不是在接收异常的地方异常,仅记录其消息内容。
除了那个设计决定,并且假设该消息是一个只读属性,我会(?)以某种方式创建一个新的Exception对象,是否有办法使新的异常对象与原始异常对象的类型相同?
这是我的代码,它没有编译 - 它偶然发现了抛出线(我尝试动态转换对象)。
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是否确保我得到了正确类型的异常?
答案 0 :(得分:11)
你在这里想做的事情并不像看起来那么容易,而且还有很多陷阱需要考虑。
请记住,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;
}
}
答案 1 :(得分:4)
您可以这样做以动态调用异常类型的构造函数:
object newEx = Activator.CreateInstance(ex.GetType(), new object[] { msg });
您的原始代码在运行时会失败,因为对于Convert.ChangeType
towork,异常类型必须实现IConvertible
并支持转换为其他异常类型,我怀疑。
答案 2 :(得分:2)
可能有点晚了,但这对你有用吗?
catch (Exception ex)
{
throw new Exception("New message", ex);
}
答案 3 :(得分:0)
您可以像这样通过反射来更改异常消息...
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;”。毫无例外。
答案 4 :(得分:0)
补充评论。
这些都是对异常消息的补充,但是我发现使用“ throw”并不能保留StackTrace-最后一条跟踪指向实际的“ throw”语句(删除根本原因位置)。
从其他地方的讨论中可以明显看出,由于CLR堆栈的限制,在某些情况下无法保留抛出问题
Throw and preserve stack trace not as expected as described by Code Analysis
解决方案:转储每个异常中的StackTrace(例如,并添加到错误消息中)和/或转储到日志记录中