Come serializzare un object Exception in C #?

Sto provando a serializzare un object Exception in C #. Tuttavia, sembra imansible perché la class Exception non è contrassegnata come [Serializable] . C’è un modo per aggirare questo?

Se qualcosa va storto durante l’esecuzione dell’applicazione, voglio essere informato con l’eccezione che si è verificata.

Il mio primo riflesso è serializzarlo.

Quello che ho fatto prima è creare una class di errore personalizzata. Questo incapsula tutte le informazioni rilevanti su un’eccezione ed è serializzabile 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; } } 

Creare una class Exception personalizzata con l’attributo [Serializable ()] . Ecco un esempio tratto dal 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) { } } 

La class Exception è contrassegnata come Serializable e implementa ISerializable. Vedere MSDN: http://msdn.microsoft.com/en-us/library/system.exception.aspx

Se si sta tentando di serializzare su XML utilizzando XmlSerializer , si verificherà un errore su tutti i membri che implementano IDictionary . Questa è una limitazione di XmlSerializer, ma la class è certamente serializzabile.

mson ha scritto: “Non sono sicuro del motivo per cui vorresti serializzare l’eccezione …”

Io serializzo le eccezioni per far scoppiare l’eccezione, attraverso un servizio web, all’object chiamante che può deserializzare, quindi rilanciare, loggare o altrimenti gestirlo.

L’ho fatto. Ho semplicemente creato una class wrapper Serializable che sostituisce l’IDictionary con un’alternativa serializzabile (array KeyValuePair)

 ///  /// A wrapper class for serializing exceptions. ///  [Serializable] [DesignerCategory( "code" )] [XmlType( AnonymousType = true, Namespace = "http://something" )] [XmlRootAttribute( Namespace = "http://something", IsNullable = false )] public class SerializableException { #region Members private KeyValuePair[] _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[] Data { get { return _Data ?? new KeyValuePair[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[0]; if ( null != collection ) collection.CopyTo( _Data, 0 ); } #endregion } 

Se stai cercando di serializzare l’eccezione per un log, potrebbe essere meglio fare un .ToString (), e quindi serializzarlo sul tuo log.

Ma ecco un articolo su come farlo, e perché. Fondamentalmente, è necessario implementare ISerializable sulla tua eccezione. Se si tratta di un’eccezione di sistema, credo che abbiano implementato questa interfaccia. Se si tratta di un’eccezione di qualcun altro, potresti essere in grado di creare una sottoclass per implementare l’interfaccia ISerializable.

Nel caso in cui qualcun altro si imbatta in questo thread (è sulla pagina uno di Google a partire da oggi), questa è una class molto utile per serializzare un object Exception in un object XElement (yay, LINQ):

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

Codice incluso per completezza:

 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(() => { // 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, eg: // 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() 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; })()) { ; } } 

Crea un costruttore protected come questo (dovresti anche contrassegnare la tua class Exception [Serializable] ):

 protected MyException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context):base(info,context) { } 

Non sono sicuro del motivo per cui vorresti serializzare l’eccezione …

Se volessi fare ciò che hai specificato, creerei una class Exception personalizzata che implementa ISerializable. Puoi scegliere di renderlo figlio di Eccezione o potresti avere una class completamente personalizzata che ha solo e fa ciò di cui hai bisogno.

Questo è un vecchio thread, ma degno di un’altra risposta.

@mson si chiedeva perché qualcuno avrebbe voluto serializzare un’eccezione. Ecco la nostra ragione per farlo:

Disponiamo di un’applicazione Prism / MVVM con viste in Silverlight e WPF, con il modello di dati nei servizi WCF. Vogliamo essere sicuri che l’accesso ai dati e gli aggiornamenti avvengano senza errori. Se c’è un errore, vogliamo saperlo immediatamente e far sapere all’utente che qualcosa potrebbe aver fallito. Le nostre applicazioni apriranno una finestra per informare l’utente di un ansible errore. L’eccezione effettiva viene quindi inviata per e-mail e archiviata in SpiceWorks per il tracciamento. Se l’errore si verifica su un servizio WCF, vogliamo restituire l’intera eccezione al client in modo che questo processo possa avvenire.

Ecco la soluzione che ho trovato che può essere gestita dai client WPF e Silverlight. I metodi sotto a in una libreria di classi “comuni” di metodi utilizzati da più applicazioni in ogni livello.

Un array di byte è facilmente serializzato da un servizio WCF. Praticamente qualsiasi object può essere convertito in un array di byte.

Ho iniziato con due metodi semplici, Object2Bytes e Bytes2Object. Questi convertono qualsiasi object in una matrice Byte e viceversa. NetDataContractSerializer proviene dalla versione Windows dello spazio dei nomi System.Runtime.Serialization.

 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 

In origine, avremmo restituito tutti i risultati come object. Se l’object che tornava dal servizio era un array di byte, allora sapevamo che era un’eccezione. Quindi chiameremmo “Bytes2Object” e lanciamo l’eccezione per la gestione.

I problemi con questo codice sono incompatibili con Silverlight. Così, per le nostre nuove applicazioni ho mantenuto i vecchi metodi per gli oggetti hard-to-serialize e ho creato un paio di nuovi metodi solo per le eccezioni. DataContractSerializer proviene anche dallo spazio dei nomi System.Runtime.Serialization, ma è presente sia nelle versioni Windows che 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 

Quando non si verificano errori, il servizio WCF restituisce 1. Se si verifica un errore, passa l’eccezione a un metodo che chiama “ExceptionToByteArray”, quindi genera un numero intero univoco dal momento attuale. Utilizza tale numero intero come chiave per memorizzare nell’array di byte per 60 secondi. Il servizio WCF restituisce quindi il valore chiave al client.

Quando il client vede che ha restituito un numero intero diverso da 1, effettua una chiamata al metodo “GetException” del servizio utilizzando tale valore chiave. Il servizio recupera l’array di byte dalla cache e lo invia al client. Il client chiama “ByteArrayToException” ed elabora l’eccezione come descritto sopra. 60 secondi è un sacco di tempo per il client per richiedere l’eccezione dal servizio. In meno di un minuto, il MemoryCache del server viene cancellato.

Penso che sia più facile rispetto alla creazione di una class Exception personalizzata. Spero che questo sia di aiuto a qualcuno in seguito.