Implementacja podstawowa, bez właściwości niestandardowych
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)
{
}
}
}
Pełna implementacja z niestandardowymi właściwościami
Pełna implementacja niestandardowego wyjątku szeregowalnego ( MySerializableException
) i sealed
wyjątku pochodnego ( MyDerivedSerializableException
).
Główne punkty dotyczące tego wdrożenia zostały podsumowane tutaj:
- Państwo musi ozdobić każdą klasę pochodzącą z
[Serializable]
atrybutem - ten atrybut nie jest dziedziczona z klasy bazowej, a jeśli nie jest określona, serializacji nie powiedzie się z SerializationException
stwierdzając, że „X Type w Assembly Y nie jest oznaczony jako możliwy do serializacji.”
- Musisz zaimplementować niestandardową serializację . Sam
[Serializable]
atrybut nie wystarczy - Exception
implementuje, ISerializable
co oznacza, że klasy pochodne muszą również implementować niestandardową serializację. Obejmuje to dwa kroki:
- Podaj konstruktor serializacji . Ten konstruktor powinien być,
private
jeśli twoja klasa jest sealed
, w przeciwnym razie powinien protected
umożliwić dostęp do klas pochodnych.
- Zastąp GetObjectData () i upewnij się, że wywołujesz
base.GetObjectData(info, context)
na końcu, aby klasa podstawowa mogła zapisać swój własny stan.
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);
}
}
}
Testy jednostkowe
Testy jednostkowe MSTest dla trzech typów wyjątków określonych powyżej.
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
nigdy się nie wywołuje .. jednak mogę nadpisać to,ToString()
co zostanieWyjątek jest już możliwy do serializacji, ale należy przesłonić
GetObjectData
metodę przechowywania zmiennych i zapewnić konstruktor, który można wywołać podczas ponownego uwodnienia obiektu.Twój przykład staje się:
źródło
Zaimplementuj ISerializable i postępuj zgodnie z normalnym wzorcem .
Musisz otagować klasę za pomocą atrybutu [Serializable] i dodać obsługę tego interfejsu, a także dodać domyślny konstruktor (opis na tej stronie, wyszukiwanie sugeruje konstruktor ). Przykład jego implementacji można zobaczyć w kodzie pod tekstem.
źródło
Aby dodać do poprawnych odpowiedzi powyżej, odkryłem, że mogę uniknąć robi ten niestandardowy serializacji rzeczy jeśli przechowywać swoje właściwości niestandardowe w
Data
kolekcji wException
klasie.Na przykład:
Prawdopodobnie jest to mniej wydajne pod względem wydajności niż rozwiązanie dostarczone przez Daniela i prawdopodobnie działa tylko dla typów „integralnych”, takich jak łańcuchy i liczby całkowite i tym podobne.
Mimo wszystko było to dla mnie bardzo łatwe i zrozumiałe.
źródło
throw;
i to naprawiało.Był świetny artykuł Erica Gunnersona na MSDN „Wyjątkowy temperament”, ale wydaje się, że został wyciągnięty. Adres URL to:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp08162001.asp
Odpowiedź Aydsmana jest poprawna, więcej informacji tutaj:
http://msdn.microsoft.com/en-us/library/ms229064.aspx
Nie mogę wymyślić żadnego przypadku użycia wyjątku z członami, których nie można serializować, ale jeśli unikniesz próby serializacji / deserializacji ich w GetObjectData i konstruktorze deserializacji, powinieneś być w porządku. Oznacz je również atrybutem [NonSerialized], bardziej jako dokumentację niż cokolwiek innego, ponieważ serializację wdrażasz samodzielnie.
źródło
Oznacz klasę jako [Serializable], chociaż nie jestem pewien, jak dobrze członek IList będzie obsługiwany przez serializator.
EDYTOWAĆ
Poniższy post jest poprawny, ponieważ Twój wyjątek niestandardowy ma konstruktor, który pobiera parametry, musisz zaimplementować ISerializable.
Jeśli użyłeś domyślnego konstruktora i odsłoniłeś dwa niestandardowe elementy z właściwościami getter / setter, możesz odejść od samego ustawienia atrybutu.
źródło
Muszę pomyśleć, że chęć serializacji wyjątku jest silnym sygnałem, że źle do czegoś podchodzisz. Jaki jest tutaj ostateczny cel? Jeśli przekazujesz wyjątek między dwoma procesami lub między osobnymi uruchomieniami tego samego procesu, większość właściwości wyjątku i tak nie będzie poprawna w drugim procesie.
Prawdopodobnie bardziej sensowne byłoby wyodrębnienie wymaganych informacji o stanie w instrukcji catch () i zarchiwizowanie ich.
źródło