更具体地说,当异常包含自定义对象时,这些对象本身可能是序列化的,也可能不是。
举个例子:
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
。
是否存在用于实现自定义例外序列化的通用代码模式?
答案 0 :(得分:363)
<强> 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
表示“程序集Y中的类型X未标记为可序列化。” < / LI>
[Serializable]
属性是不够的 - Exception
实现ISerializable
,这意味着派生类还必须实现自定义序列化。这涉及两个步骤:
private
,则此构造函数应为sealed
,否则应为protected
以允许访问派生类。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()");
}
}
}
答案 1 :(得分:18)
异常已经可序列化,但您需要覆盖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>));
}
}
答案 2 :(得分:7)
实施ISerializable,并按照normal pattern执行此操作。
您需要使用[Serializable]属性标记类,并添加对该接口的支持,并添加隐含的构造函数(在该页面上描述,搜索暗示构造函数)。您可以在文本下方的代码中看到它的实现示例。
答案 3 :(得分:5)
为了添加上面的正确答案,我发现如果我将自定义属性存储在Exception
类的Data
collection中,我可以避免执行此自定义序列化工作。
E.g:
[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; }
}
// ...
}
在性能方面,这可能比the solution provided by Daniel效率低,并且可能仅适用于&#34;积分&#34;类似字符串和整数等。
对我来说,这仍然很容易理解。
答案 4 :(得分:1)
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]属性标记它们,更多的是作为文档而不是其他任何内容,因为您自己实现了序列化。
答案 5 :(得分:0)
使用[Serializable]标记该类,但我不确定序列化程序将如何处理IList成员。
修改强>
下面的帖子是正确的,因为您的自定义异常具有接受参数的构造函数,您必须实现ISerializable。
如果您使用了默认构造函数并使用getter / setter属性公开了两个自定义成员,那么只需设置属性即可。
答案 6 :(得分:0)
在 .NET Core 中,.Net 5.0 及更高版本不使用 Serializable,因为 Microsoft 遵循 BinaryFormatter 中的安全威胁做法。
使用示例存储在数据集合中
答案 7 :(得分:-5)
我必须认为想要序列化异常强烈表明你采取了错误的方法。这里的最终目标是什么?如果您在两个进程之间或同一进程的单独运行之间传递异常,那么异常的大部分属性无论如何都不会在另一个进程中有效。
在catch()语句中提取所需的状态信息并将其归档可能更有意义。