Implémentation de base, sans propriétés personnalisées
SerializableExceptionWithoutCustomProperties.cs:
namespace SerializableExceptions
{
using System;
using System.Runtime.Serialization;
[Serializable]
// Important: This attribute is NOT inherited from Exception, and MUST be specified
// otherwise serialization will fail with a SerializationException stating that
// "Type X in Assembly Y is not marked as serializable."
public class SerializableExceptionWithoutCustomProperties : Exception
{
public SerializableExceptionWithoutCustomProperties()
{
}
public SerializableExceptionWithoutCustomProperties(string message)
: base(message)
{
}
public SerializableExceptionWithoutCustomProperties(string message, Exception innerException)
: base(message, innerException)
{
}
// Without this constructor, deserialization will fail
protected SerializableExceptionWithoutCustomProperties(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
}
Implémentation complète, avec des propriétés personnalisées
Implémentation complète d'une exception sérialisable personnalisée ( MySerializableException
) et d'une sealed
exception dérivée ( MyDerivedSerializableException
).
Les principaux points concernant cette implémentation sont résumés ici:
- Vous devez décorer chaque classe dérivée avec l'
[Serializable]
attribut - Cet attribut n'est pas hérité de la classe de base et s'il n'est pas spécifié, la sérialisation échouera avec un SerializationException
message indiquant que «le type X dans l'assembly Y n'est pas marqué comme sérialisable».
- Vous devez implémenter la sérialisation personnalisée . L'
[Serializable]
attribut seul ne suffit pas - Exception
implémente, ISerializable
ce qui signifie que vos classes dérivées doivent également implémenter la sérialisation personnalisée. Cela implique deux étapes:
- Fournissez un constructeur de sérialisation . Ce constructeur devrait être
private
si votre classe l'est sealed
, sinon il devrait être protected
pour permettre l'accès aux classes dérivées.
- Surchargez GetObjectData () et assurez-vous d'appeler
base.GetObjectData(info, context)
à la fin, afin de laisser la classe de base enregistrer son propre état.
SerializableExceptionWithCustomProperties.cs:
namespace SerializableExceptions
{
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Security.Permissions;
[Serializable]
// Important: This attribute is NOT inherited from Exception, and MUST be specified
// otherwise serialization will fail with a SerializationException stating that
// "Type X in Assembly Y is not marked as serializable."
public class SerializableExceptionWithCustomProperties : Exception
{
private readonly string resourceName;
private readonly IList<string> validationErrors;
public SerializableExceptionWithCustomProperties()
{
}
public SerializableExceptionWithCustomProperties(string message)
: base(message)
{
}
public SerializableExceptionWithCustomProperties(string message, Exception innerException)
: base(message, innerException)
{
}
public SerializableExceptionWithCustomProperties(string message, string resourceName, IList<string> validationErrors)
: base(message)
{
this.resourceName = resourceName;
this.validationErrors = validationErrors;
}
public SerializableExceptionWithCustomProperties(string message, string resourceName, IList<string> validationErrors, Exception innerException)
: base(message, innerException)
{
this.resourceName = resourceName;
this.validationErrors = validationErrors;
}
[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
// Constructor should be protected for unsealed classes, private for sealed classes.
// (The Serializer invokes this constructor through reflection, so it can be private)
protected SerializableExceptionWithCustomProperties(SerializationInfo info, StreamingContext context)
: base(info, context)
{
this.resourceName = info.GetString("ResourceName");
this.validationErrors = (IList<string>)info.GetValue("ValidationErrors", typeof(IList<string>));
}
public string ResourceName
{
get { return this.resourceName; }
}
public IList<string> ValidationErrors
{
get { return this.validationErrors; }
}
[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
{
throw new ArgumentNullException("info");
}
info.AddValue("ResourceName", this.ResourceName);
// Note: if "List<T>" isn't serializable you may need to work out another
// method of adding your list, this is just for show...
info.AddValue("ValidationErrors", this.ValidationErrors, typeof(IList<string>));
// MUST call through to the base class to let it save its own state
base.GetObjectData(info, context);
}
}
}
DerivedSerializableExceptionWithAdditionalCustomProperties.cs:
namespace SerializableExceptions
{
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Security.Permissions;
[Serializable]
public sealed class DerivedSerializableExceptionWithAdditionalCustomProperty : SerializableExceptionWithCustomProperties
{
private readonly string username;
public DerivedSerializableExceptionWithAdditionalCustomProperty()
{
}
public DerivedSerializableExceptionWithAdditionalCustomProperty(string message)
: base(message)
{
}
public DerivedSerializableExceptionWithAdditionalCustomProperty(string message, Exception innerException)
: base(message, innerException)
{
}
public DerivedSerializableExceptionWithAdditionalCustomProperty(string message, string username, string resourceName, IList<string> validationErrors)
: base(message, resourceName, validationErrors)
{
this.username = username;
}
public DerivedSerializableExceptionWithAdditionalCustomProperty(string message, string username, string resourceName, IList<string> validationErrors, Exception innerException)
: base(message, resourceName, validationErrors, innerException)
{
this.username = username;
}
[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
// Serialization constructor is private, as this class is sealed
private DerivedSerializableExceptionWithAdditionalCustomProperty(SerializationInfo info, StreamingContext context)
: base(info, context)
{
this.username = info.GetString("Username");
}
public string Username
{
get { return this.username; }
}
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
{
throw new ArgumentNullException("info");
}
info.AddValue("Username", this.username);
base.GetObjectData(info, context);
}
}
}
Tests unitaires
Tests unitaires MSTest pour les trois types d'exceptions définis ci-dessus.
UnitTests.cs:
namespace SerializableExceptions
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class UnitTests
{
private const string Message = "The widget has unavoidably blooped out.";
private const string ResourceName = "Resource-A";
private const string ValidationError1 = "You forgot to set the whizz bang flag.";
private const string ValidationError2 = "Wally cannot operate in zero gravity.";
private readonly List<string> validationErrors = new List<string>();
private const string Username = "Barry";
public UnitTests()
{
validationErrors.Add(ValidationError1);
validationErrors.Add(ValidationError2);
}
[TestMethod]
public void TestSerializableExceptionWithoutCustomProperties()
{
Exception ex =
new SerializableExceptionWithoutCustomProperties(
"Message", new Exception("Inner exception."));
// Save the full ToString() value, including the exception message and stack trace.
string exceptionToString = ex.ToString();
// Round-trip the exception: Serialize and de-serialize with a BinaryFormatter
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream())
{
// "Save" object state
bf.Serialize(ms, ex);
// Re-use the same stream for de-serialization
ms.Seek(0, 0);
// Replace the original exception with de-serialized one
ex = (SerializableExceptionWithoutCustomProperties)bf.Deserialize(ms);
}
// Double-check that the exception message and stack trace (owned by the base Exception) are preserved
Assert.AreEqual(exceptionToString, ex.ToString(), "ex.ToString()");
}
[TestMethod]
public void TestSerializableExceptionWithCustomProperties()
{
SerializableExceptionWithCustomProperties ex =
new SerializableExceptionWithCustomProperties(Message, ResourceName, validationErrors);
// Sanity check: Make sure custom properties are set before serialization
Assert.AreEqual(Message, ex.Message, "Message");
Assert.AreEqual(ResourceName, ex.ResourceName, "ex.ResourceName");
Assert.AreEqual(2, ex.ValidationErrors.Count, "ex.ValidationErrors.Count");
Assert.AreEqual(ValidationError1, ex.ValidationErrors[0], "ex.ValidationErrors[0]");
Assert.AreEqual(ValidationError2, ex.ValidationErrors[1], "ex.ValidationErrors[1]");
// Save the full ToString() value, including the exception message and stack trace.
string exceptionToString = ex.ToString();
// Round-trip the exception: Serialize and de-serialize with a BinaryFormatter
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream())
{
// "Save" object state
bf.Serialize(ms, ex);
// Re-use the same stream for de-serialization
ms.Seek(0, 0);
// Replace the original exception with de-serialized one
ex = (SerializableExceptionWithCustomProperties)bf.Deserialize(ms);
}
// Make sure custom properties are preserved after serialization
Assert.AreEqual(Message, ex.Message, "Message");
Assert.AreEqual(ResourceName, ex.ResourceName, "ex.ResourceName");
Assert.AreEqual(2, ex.ValidationErrors.Count, "ex.ValidationErrors.Count");
Assert.AreEqual(ValidationError1, ex.ValidationErrors[0], "ex.ValidationErrors[0]");
Assert.AreEqual(ValidationError2, ex.ValidationErrors[1], "ex.ValidationErrors[1]");
// Double-check that the exception message and stack trace (owned by the base Exception) are preserved
Assert.AreEqual(exceptionToString, ex.ToString(), "ex.ToString()");
}
[TestMethod]
public void TestDerivedSerializableExceptionWithAdditionalCustomProperty()
{
DerivedSerializableExceptionWithAdditionalCustomProperty ex =
new DerivedSerializableExceptionWithAdditionalCustomProperty(Message, Username, ResourceName, validationErrors);
// Sanity check: Make sure custom properties are set before serialization
Assert.AreEqual(Message, ex.Message, "Message");
Assert.AreEqual(ResourceName, ex.ResourceName, "ex.ResourceName");
Assert.AreEqual(2, ex.ValidationErrors.Count, "ex.ValidationErrors.Count");
Assert.AreEqual(ValidationError1, ex.ValidationErrors[0], "ex.ValidationErrors[0]");
Assert.AreEqual(ValidationError2, ex.ValidationErrors[1], "ex.ValidationErrors[1]");
Assert.AreEqual(Username, ex.Username);
// Save the full ToString() value, including the exception message and stack trace.
string exceptionToString = ex.ToString();
// Round-trip the exception: Serialize and de-serialize with a BinaryFormatter
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream())
{
// "Save" object state
bf.Serialize(ms, ex);
// Re-use the same stream for de-serialization
ms.Seek(0, 0);
// Replace the original exception with de-serialized one
ex = (DerivedSerializableExceptionWithAdditionalCustomProperty)bf.Deserialize(ms);
}
// Make sure custom properties are preserved after serialization
Assert.AreEqual(Message, ex.Message, "Message");
Assert.AreEqual(ResourceName, ex.ResourceName, "ex.ResourceName");
Assert.AreEqual(2, ex.ValidationErrors.Count, "ex.ValidationErrors.Count");
Assert.AreEqual(ValidationError1, ex.ValidationErrors[0], "ex.ValidationErrors[0]");
Assert.AreEqual(ValidationError2, ex.ValidationErrors[1], "ex.ValidationErrors[1]");
Assert.AreEqual(Username, ex.Username);
// Double-check that the exception message and stack trace (owned by the base Exception) are preserved
Assert.AreEqual(exceptionToString, ex.ToString(), "ex.ToString()");
}
}
}
GetObjectData
n'est jamais invoqué ... mais je peux remplacerToString()
ce qui estL'exception est déjà sérialisable, mais vous devez remplacer la
GetObjectData
méthode pour stocker vos variables et fournir un constructeur qui peut être appelé lors de la réhydratation de votre objet.Votre exemple devient donc:
la source
Implémentez ISerializable et suivez le modèle normal pour ce faire.
Vous devez étiqueter la classe avec l'attribut [Serializable] et ajouter la prise en charge de cette interface, et également ajouter le constructeur implicite (décrit sur cette page, la recherche implique un constructeur ). Vous pouvez voir un exemple de son implémentation dans le code sous le texte.
la source
Pour ajouter aux bonnes réponses ci-dessus, j'ai découvert que je peux éviter de faire ce truc de sérialisation personnalisé si je stocke mes propriétés personnalisées dans la
Data
collection de laException
classe.Par exemple:
Ceci est probablement moins efficace en termes de performances que la solution fournie par Daniel et ne fonctionne probablement que pour les types "intégraux" comme les chaînes et les entiers et similaires.
C'était toujours très facile et très compréhensible pour moi.
la source
throw;
ce problème .Il y avait un excellent article d'Eric Gunnerson sur MSDN "L'exception bien tempérée" mais il semble avoir été retiré. L'URL était:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp08162001.asp
La réponse d'Aydsman est correcte, plus d'informations ici:
http://msdn.microsoft.com/en-us/library/ms229064.aspx
Je ne peux penser à aucun cas d'utilisation pour une exception avec des membres non sérialisables, mais si vous évitez d'essayer de les sérialiser / désérialiser dans GetObjectData et le constructeur de désérialisation, vous devriez être OK. Marquez-les également avec l'attribut [NonSerialized], plus comme documentation qu'autre chose, puisque vous implémentez vous-même la sérialisation.
la source
Marquez la classe avec [Serializable], même si je ne sais pas dans quelle mesure un membre IList sera géré par le sérialiseur.
ÉDITER
Le message ci-dessous est correct, car votre exception personnalisée a un constructeur qui prend des paramètres, vous devez implémenter ISerializable.
Si vous avez utilisé un constructeur par défaut et exposé les deux membres personnalisés avec des propriétés getter / setter, vous pouvez vous en sortir en définissant simplement l'attribut.
la source
Je dois penser que vouloir sérialiser une exception est une forte indication que vous adoptez la mauvaise approche pour quelque chose. Quel est le but ultime, ici? Si vous passez l'exception entre deux processus, ou entre des exécutions distinctes du même processus, la plupart des propriétés de l'exception ne seront pas valides de toute façon.
Il serait probablement plus logique d'extraire les informations d'état que vous souhaitez dans l'instruction catch () et de les archiver.
la source