Каков правильный способ сделать custom.NET исключение сериализуемо?
более конкретно, когда исключение содержит пользовательские объекты,которые могут быть или не быть сериализуемыми.
рассмотрим пример:
public class MyException : Exception
{
private readonly string resourceName;
private readonly IList<string> validationErrors;
public MyException(string resourceName, IList<string> validationErrors)
{
this.resourceName = resourceName;
this.validationErrors = validationErrors;
}
public string ResourceName
{
get { return this.resourceName; }
}
public IList<string> ValidationErrors
{
get { return this.validationErrors; }
}
}
если это исключение сериализации и де-сериализации свойства (ResourceName и ValidationErrors) не будут сохранены. Свойства вернут null.
существует ли общий шаблон кода для реализации сериализации для пользовательского исключения?
7 ответов:
базовая реализация, без пользовательских свойств
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) { } } }полная реализация, с пользовательскими свойствами
полная реализация пользовательского сериализуемого исключения (
MySerializableException), и производногоsealedисключение (MyDerivedSerializableException).основные моменты об этой реализации суммируются здесь:
- вы должны украсьте каждый производный класс с
[Serializable]атрибут - Этот атрибут не наследуется от базового класса, и если он не указан, сериализация не будетSerializationExceptionуказав, что " тип X в сборке Y не помечен как сериализуемый."- вы необходимо реализовать пользовательскую сериализацию. Элемент
[Serializable]одного атрибута недостаточно -ExceptionосуществляетISerializableчто означает, что производные классы также должны реализовывать пользовательские сериализация. Это включает в себя два этапа:
- предоставить конструктор сериализации. Этот конструктор должен быть
privateесли ваш классsealed, иначе он должен быть!--14--> разрешить доступ к производным классам.- Переопределить GetObjectData () и убедитесь, что вы звоните через
base.GetObjectData(info, context)в конце, чтобы позволить базовому классу сохранить свой собственный государство.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); } } }
Тесты
модульные тесты MSTest для трех типов исключений, определенных выше.
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метод для хранения переменных и предоставления конструктора, который может быть вызван при повторном увлажнении объекта.Так что ваш пример становится:
[Serializable()] public class MyException : Exception { private readonly string resourceName; private readonly IList<string> validationErrors; public MyException(string resourceName, IList<string> validationErrors) { this.resourceName = resourceName; this.validationErrors = validationErrors; } public string ResourceName { get { return this.resourceName; } } public IList<string> ValidationErrors { get { return this.validationErrors; } } [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter=true)] protected MyException(SerializationInfo info, StreamingContext context) : base (info, context) { this.resourceName = info.GetString("MyException.ResourceName"); this.validationErrors = info.GetValue("MyException.ValidationErrors", typeof(IList<string>)); } [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter=true)] public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue("MyException.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("MyException.ValidationErrors", this.ValidationErrors, typeof(IList<string>)); } }
реализовать ISerializable, и следовать нормальный шаблон для этого.
вам нужно пометить класс атрибутом [Serializable] и добавить поддержку этого интерфейса, а также добавить подразумеваемый конструктор (описанный на этой странице, поиск подразумевает конструктор). Вы можете увидеть пример его реализации в коде ниже по тексту.
чтобы добавить к правильным ответам выше, я обнаружил, что могу избежать этой пользовательской сериализации, если я сохраню свои пользовательские свойства в
Dataколлекция наExceptionкласса.например:
[Serializable] public class JsonReadException : Exception { // ... public string JsonFilePath { get { return Data[@"_jsonFilePath"] as string; } private set { Data[@"_jsonFilePath"] = value; } } public string Json { get { return Data[@"_json"] as string; } private set { Data[@"_json"] = value; } } // ... }наверное, это менее эффективно с точки зрения производительности, чем решение, предоставленное Даниэлем и, вероятно, работает только для "интегральных" типов, таких как строки и целые числа и тому подобное.
тем не менее это было очень легко и очень понятно для меня.
раньше была отличная статья от Эрика Ганнерсона на MSDN "the well-tempered exception", но, похоже, ее вытащили. URL-адрес был:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp08162001.asp
ответ Aydsman правильный, больше информации здесь:
http://msdn.microsoft.com/en-us/library/ms229064.aspx
Я не могу придумать ни одного прецедента для исключения с несериализуемыми членами, но если вы не пытаетесь сериализовать/десериализовать их в GetObjectData и конструкторе десериализации, вы должны быть в порядке. Также отметьте их атрибутом [NonSerialized], больше как документацию, чем что-либо еще, так как вы сами реализуете сериализацию.
отметьте класс с помощью [Serializable], хотя я не уверен, насколько хорошо член IList будет обрабатываться сериализатором.
EDIT
сообщение ниже правильно, потому что ваше пользовательское исключение имеет конструктор, который принимает параметры, вы должны реализовать ISerializable.
Если вы использовали конструктор по умолчанию и выставили два пользовательских элемента со свойствами getter/setter, вы можете просто установить атрибут.
Я должен думать, что желание сериализовать исключение является сильным признаком того, что вы принимаете неправильный подход к чему-то. Какова здесь конечная цель? Если вы передаете исключение между двумя процессами или между отдельными запусками одного и того же процесса, то большинство свойств исключения все равно не будут действительны в другом процессе.
вероятно, было бы более целесообразно извлечь информацию о состоянии, которую вы хотите в операторе catch (), и архив, который.
Comments