如何在C#中序列化异常对象?

79

我试图在C#中序列化一个异常对象。然而,由于异常类未标记为[Serializable],因此似乎不可能进行序列化。是否有方法可以解决这个问题?

如果应用程序执行期间出现问题,我希望通过发生的异常得到通知。

我的第一反应是对其进行序列化。


1
还有一个重复的问题:什么是使自定义.NET异常可序列化的正确方法? - Jared Moore
2
System.Exception 类被标记为可序列化。https://msdn.microsoft.com/zh-cn/library/system.exception(v=vs.110).aspx - Jodrell
11个回答

54

创建带有[Serializable()]属性的自定义异常类。以下是从MSDN获取的示例:

[Serializable()]
public class InvalidDepartmentException : System.Exception
{
    public InvalidDepartmentException() { }
    public InvalidDepartmentException(string message) : base(message) { }
    public InvalidDepartmentException(string message, System.Exception inner) : base(message, inner) { }

    // Constructor needed for serialization 
    // when exception propagates from a remoting server to the client.
    protected InvalidDepartmentException(System.Runtime.Serialization.SerializationInfo info,
        System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
}

这让我解决了一个关于异常和远程调用的问题。 - 0E322070
1
消息内容怎么样?序列化构造函数是否应该实际执行某些操作以从流上下文中提取消息或其他内容? - Lucas
请注意,如果您将此用于未标记为可序列化的内部异常,则在序列化时会出现运行时错误。如果内部异常来自.NET框架,则不是问题,但如果使用自定义异常类并忘记Serializable属性,则会导致失败。 - rybo103
3
System.Net.Sockets.SocketExceptionSystem.Net.Http.HttpRequestException不可序列化? - Kiquenet

45

异常类被标记为可序列化并实现了ISerializable接口。请参见MSDN:http://msdn.microsoft.com/en-us/library/system.exception.aspx

如果您尝试使用XmlSerializer将数据序列化为XML,那么任何实现了IDictionary的成员都会出现错误。这是XmlSerializer的一个限制,但该类确实是可序列化的。


另一个限制是:如果您正在使用Silverlight和WCF,则来回传递的对象不能为其序列化进行ISerializable。我试图传递一个异常对象,但由于Silverlight中不可用ISerializable,所以它无法工作。 - John Gilmer
这个回答很到位。我想补充一点,由于 XML 序列化对于异常来说是一个问题,所以我使用了 Newtonsoft 的 JSON 序列化,这样就完美解决了。 - Ayushmati

45

我之前所做的是创建一个自定义错误类。这个类包含了有关异常的所有相关信息,并且可以进行XML序列化。

[Serializable]
public class Error
{
    public DateTime TimeStamp { get; set; }
    public string Message { get; set; }
    public string StackTrace { get; set; }

    public Error()
    {
        this.TimeStamp = DateTime.Now;
    }

    public Error(string Message) : this()
    {
        this.Message = Message;
    }

    public Error(System.Exception ex) : this(ex.Message)
    {
        this.StackTrace = ex.StackTrace;
    }

    public override string ToString()
    {
        return this.Message + this.StackTrace;
    }
}

2
请参考“设计自定义异常”http://msdn.microsoft.com/en-us/library/ms229064(v=VS.100).aspx。 - user295190
10
问题在于,如果存在一个内部异常实际上包含了细节信息,你将不会包括这些信息。 - greektreat

17

mson写道:“我不确定为什么你要序列化异常…”

我将异常序列化,通过Web服务将其传递到调用对象,该对象可以反序列化、重新抛出、记录或以其他方式处理它。

我这样做了。我只是创建了一个可序列化的包装类,用可序列化的替代品(KeyValuePair数组)替换了IDictionary。

/// <summary>
/// A wrapper class for serializing exceptions.
/// </summary>
[Serializable] [DesignerCategory( "code" )] [XmlType( AnonymousType = true, Namespace = "http://something" )] [XmlRootAttribute( Namespace = "http://something", IsNullable = false )] public class SerializableException
{
    #region Members
    private KeyValuePair<object, object>[] _Data; //This is the reason this class exists. Turning an IDictionary into a serializable object
    private string _HelpLink = string.Empty;
    private SerializableException _InnerException;
    private string _Message = string.Empty;
    private string _Source = string.Empty;
    private string _StackTrace = string.Empty;
    #endregion

    #region Constructors
    public SerializableException()
    {
    }

    public SerializableException( Exception exception ) : this()
    {
        setValues( exception );
    }
    #endregion

    #region Properties
    public string HelpLink { get { return _HelpLink; } set { _HelpLink = value; } }
    public string Message { get { return _Message; } set { _Message = value; } }
    public string Source { get { return _Source; } set { _Source = value; } }
    public string StackTrace { get { return _StackTrace; } set { _StackTrace = value; } }
    public SerializableException InnerException { get { return _InnerException; } set { _InnerException = value; } } // Allow null to be returned, so serialization doesn't cascade until an out of memory exception occurs
    public KeyValuePair<object, object>[] Data { get { return _Data ?? new KeyValuePair<object, object>[0]; } set { _Data = value; } }
    #endregion

    #region Private Methods
    private void setValues( Exception exception )
    {
        if ( null != exception )
        {
            _HelpLink = exception.HelpLink ?? string.Empty;
            _Message = exception.Message ?? string.Empty;
            _Source = exception.Source ?? string.Empty;
            _StackTrace = exception.StackTrace ?? string.Empty;
            setData( exception.Data );
            _InnerException = new SerializableException( exception.InnerException );
        }
    }

    private void setData( ICollection collection )
    {
        _Data = new KeyValuePair<object, object>[0];

        if ( null != collection )
            collection.CopyTo( _Data, 0 );
    }
    #endregion
}

3
按Ctrl+K,Ctrl+D即可停止抱怨文字自动换行。 - Antony Booth
4
@AdamNaylor 看看(例如)属性。我发现只需要看6行代码就能看到所有的属性最方便,而不是(至少,6x11=)66行代码。你只是(就像我现在一样)表达了“品味”。因此,“Blurgh!”更多地反映了你表达自己的方式,而不是正确的代码格式。但我猜你已经意识到了这一点... - Mike de Klerk
VS会自然地抵制过长的代码行,品味并不是其中的因素。 - Adam Naylor
我刚刚建议了一个替代方案,这样你的代码就可以支持序列化一些无法序列化的异常,比如SqlException。非常感谢你分享你的代码。 - Julio Nobre
出于好奇,如果“Data”包含无法序列化的值会发生什么? - canon
这不能替代 System.Exception,因为只有这个特殊类型(及其子类型)可以被捕获和抛出(看起来是这样)。 - HankCa

11

如果你要将异常序列化为日志,最好使用.ToString()方法,然后将其序列化到日志中。

这里有一篇关于如何以及为什么要这样做的文章。基本上,你需要在异常类上实现ISerializable接口。如果是系统异常,我认为它们已经实现了该接口。如果是其他人的异常,你可以尝试继承它来实现ISerializable接口。


5
现在,将近两年过去了,我被踩了一下。踩我的人能解释一下吗? - mmr
6
可能是因为文章链接失效了? - thepirat000
https://winterdom.com/2007/01/16/makeexceptionclassesserializable 的新文章链接。 - Allen Clark Copeland Jr

8
以下是将Exception对象序列化为XElement对象的非常有用的类(使用LINQ):

http://seattlesoftware.wordpress.com/2008/08/22/serializing-exceptions-to-xml/

为了完整性,附上代码:
using System;
using System.Collections;
using System.Linq;
using System.Xml.Linq;
 
public class ExceptionXElement : XElement
{
    public ExceptionXElement(Exception exception)
        : this(exception, false)
    { ; }
 

    public ExceptionXElement(Exception exception, bool omitStackTrace)
        : base(new Func<XElement>(() =>
        {
            // Validate arguments
            if (exception == null)
            {
                throw new ArgumentNullException("exception");
            }
            
            // The root element is the Exception's type
            XElement root = new XElement(exception.GetType().ToString());
            if (exception.Message != null)
            {
                root.Add(new XElement("Message", exception.Message));
            }
            
            // StackTrace can be null, e.g.:
            // new ExceptionAsXml(new Exception())
            if (!omitStackTrace && exception.StackTrace != null)
            {
                vroot.Add(
                    new XElement("StackTrace",
                    from frame in exception.StackTrace.Split('\n')
                    let prettierFrame = frame.Substring(6).Trim()
                    select new XElement("Frame", prettierFrame))
                );
            }
            
            // Data is never null; it's empty if there is no data
            if (exception.Data.Count > 0)
            {
                root.Add(
                    new XElement("Data",
                        from entry in exception.Data.Cast<DictionaryEntry>()
                        let key = entry.Key.ToString()
                        let value = (entry.Value == null) ? "null" : entry.Value.ToString()
                        select new XElement(key, value))
                );
            }
            
            // Add the InnerException if it exists
            if (exception.InnerException != null)
            {
                root.Add(new ExceptionXElement(exception.InnerException, omitStackTrace));
            }
            return root;
        })())
    { ; }
}

5
创建一个像这样的protected构造函数(同时你应该将你的Exception类标记为[Serializable]):
protected MyException(System.Runtime.Serialization.SerializationInfo info,
    System.Runtime.Serialization.StreamingContext context):base(info,context)
{
}

2

我曾经遇到过类似的问题,需要记录应用程序中抛出的异常细节,但是它包含了一个非可序列化的成员,因此无法进行序列化。

为了解决这个问题,可以通过调用Exception.ToString()方法来返回包含Exception细节的字符串。

string bar;

if (foo is Exception exception)
{
   bar = exception.ToString();
}

来源: Exception.ToString 方法


0

这是一个旧的帖子,但值得再次回答。

@mson想知道为什么有人想序列化异常。以下是我们这样做的原因:

我们有一个使用Prism/MVVM的应用程序,其中视图在Silverlight和WPF中都有,数据模型在WCF服务中。我们希望确保数据访问和更新不会出错。如果出现错误,我们希望立即得知,并让用户知道可能发生了某些故障。我们的应用程序将弹出一个窗口,通知用户可能出现错误。然后,实际的异常将通过电子邮件发送给我们,并存储在SpiceWorks中进行跟踪。如果错误发生在WCF服务上,我们希望将完整的异常返回给客户端,以便进行此过程。

以下是我想出的解决方案,可以由WPF和Silverlight客户端处理。下面的方法在多个应用程序的“Common”类库中使用。

从WCF服务轻松序列化字节数组。几乎任何对象都可以转换为字节数组。

我从两个简单的方法Object2Bytes和Bytes2Object开始。这些方法将任何对象转换为字节数组并返回。NetDataContractSerializer来自System.Runtime.Serialization命名空间的Windows版本。

Public Function Object2Bytes(ByVal value As Object) As Byte()
    Dim bytes As Byte()
    Using ms As New MemoryStream
        Dim ndcs As New NetDataContractSerializer()
        ndcs.Serialize(ms, value)
        bytes = ms.ToArray
    End Using
    Return bytes
End Function

Public Function Bytes2Object(ByVal bytes As Byte()) As Object
    Using ms As New MemoryStream(bytes)
        Dim ndcs As New NetDataContractSerializer
        Return ndcs.Deserialize(ms)
    End Using
End Function

最初,我们会将所有的结果作为一个 Object 返回。如果从服务返回的对象是字节数组,则说明它是异常。然后我们会调用“Bytes2Object”并抛出异常以进行处理。

这段代码的问题在于它与 Silverlight 不兼容。因此,针对我们的新应用程序,我保留了旧的方法来处理难以序列化的对象,并创建了一对新的方法来处理异常。DataContractSerializer 也来自于 System.Runtime.Serialization 命名空间,但它在 Windows 和 Silverlight 版本中都存在。

Public Function ExceptionToByteArray(obj As Object) As Byte()
    If obj Is Nothing Then Return Nothing
    Using ms As New MemoryStream
        Dim dcs As New DataContractSerializer(GetType(Exception))
        dcs.WriteObject(ms, obj)
        Return ms.ToArray
    End Using
End Function

Public Function ByteArrayToException(bytes As Byte()) As Exception
    If bytes Is Nothing OrElse bytes.Length = 0 Then
        Return Nothing
    End If
    Using ms As New MemoryStream
        Dim dcs As New DataContractSerializer(GetType(Exception))
        ms.Write(bytes, 0, bytes.Length)
        Return CType(dcs.ReadObject(ms), Exception)
    End Using
End Function

当没有错误发生时,WCF服务返回1。如果发生错误,则将异常传递给调用“ExceptionToByteArray”的方法,然后从当前时间生成一个唯一的整数。它使用该整数作为键来缓存字节数组60秒钟。然后,WCF服务将键值返回给客户端。

当客户端看到返回的整数不是1时,它使用该键值调用服务的“GetException”方法。服务从缓存中获取字节数组并将其发送回客户端。客户端调用“ByteArrayToException”并按我上面描述的方式处理异常。60秒足够客户端从服务请求异常。在不到一分钟的时间内,服务器的MemoryCache就会被清除。

我认为这比创建自定义异常类更容易。我希望这对以后的某些人有所帮助。


1
这种实现的问题在于客户端加载的程序集不一定与服务器相同,当尝试反序列化内部异常时可能会抛出SerializationException。 - Christoph

-2

我不确定为什么您想要序列化异常...

如果我确实想要执行您指定的操作,我会创建一个实现ISerializable接口的自定义异常类。您可以选择将其作为Exception的子类,或者您可以将其作为完全自定义的类,只具有并执行您需要的功能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接