使自定义 .NET 异常可序列化的正确方法是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/94488/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is the correct way to make a custom .NET Exception serializable?
提问by Daniel Fortunov
More specifically, when the exception contains custom objects which may or may not themselves be serializable.
更具体地说,当异常包含自定义对象时,这些对象本身可能是可序列化的,也可能不是。
Take this example:
拿这个例子:
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; }
}
}
If this Exception is serialized and de-serialized, the two custom properties (ResourceNameand ValidationErrors) will not be preserved. The properties will return null.
如果将此 Exception 序列化和反序列化,则不会保留两个自定义属性 (ResourceName和ValidationErrors)。属性将返回null。
Is there a common code pattern for implementing serialization for custom exception?
是否有用于实现自定义异常序列化的通用代码模式?
回答by Daniel Fortunov
Base implementation, without custom properties
基本实现,没有自定义属性
SerializableExceptionWithoutCustomProperties.cs:
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)
{
}
}
}
Full implementation, with custom properties
完全实现,带有自定义属性
Complete implementation of a custom serializable exception (MySerializableException), and a derived sealedexception (MyDerivedSerializableException).
自定义可序列化异常 ( MySerializableException) 和派生sealed异常 ( MyDerivedSerializableException) 的完整实现。
The main points about this implementation are summarized here:
此处总结了有关此实现的要点:
- You must decorate each derived class with the
[Serializable]attribute— This attribute is not inherited from the base class, and if it is not specified, serialization will fail with aSerializationExceptionstating that "Type X in Assembly Y is not marked as serializable." - You must implement custom serialization. The
[Serializable]attribute alone is not enough —ExceptionimplementsISerializablewhich means your derived classes must also implement custom serialization. This involves two steps:- Provide a serialization constructor. This constructor should be
privateif your class issealed, otherwise it should beprotectedto allow access to derived classes. - Override GetObjectData()and make sure you call through to
base.GetObjectData(info, context)at the end, in order to let the base class save its own state.
- Provide a serialization constructor. This constructor should be
- 您必须用
[Serializable]属性修饰每个派生类— 此属性不是从基类继承的,如果未指定,序列化将失败,SerializationException并显示“Assembly Y 中的类型 X 未标记为可序列化”。 - 您必须实现自定义序列化。在
[Serializable]单独的属性是不够的-Exception工具ISerializable,这意味着您的派生类还必须实现自定义序列。这包括两个步骤:- 提供一个序列化构造函数。
private如果您的类是sealed,则此构造函数应为,否则应protected允许访问派生类。 - 覆盖 GetObjectData()并确保
base.GetObjectData(info, context)在最后调用 to ,以便让基类保存自己的状态。
- 提供一个序列化构造函数。
SerializableExceptionWithCustomProperties.cs:
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:
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);
}
}
}
Unit Tests
单元测试
MSTest unit tests for the three exception types defined above.
MSTest 对上面定义的三种异常类型进行单元测试。
UnitTests.cs:
单元测试.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()");
}
}
}
回答by Adrian Clark
Exception is already serializable, but you need to override the GetObjectDatamethod to store your variables and provide a constructor which can be called when re-hydrating your object.
异常已经是可序列化的,但是您需要覆盖该GetObjectData方法来存储您的变量并提供一个构造函数,该构造函数可以在重新水合您的对象时调用。
So your example becomes:
所以你的例子变成:
[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>));
}
}
回答by Uwe Keim
To add to the correct answers above, I discovered that I can avoid doing this custom serialization stuff if I store my custom properties in the Datacollectionof the Exceptionclass.
为了补充上面的正确答案,我发现如果我将自定义属性存储在类的Data集合中,我可以避免执行这种自定义序列化的事情Exception。
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; }
}
// ...
}
Probably this is less efficient in terms of performance than the solution provided by Danieland probably only works for "integral" types like strings and integers and the like.
就性能而言,这可能比Daniel 提供的解决方案效率低,并且可能仅适用于“整数”类型,如字符串和整数等。
Still it was very easy and very understandable for me.
对我来说,这仍然很容易,也很容易理解。
回答by Lasse V. Karlsen
Implement ISerializable, and follow the normal patternfor doing this.
实现 ISerializable,并按照正常模式执行此操作。
You need to tag the class with the [Serializable] attribute, and add support for that interface, and also add the implied constructor (described on that page, search for implies a constructor). You can see an example of its implementation in the code below the text.
您需要使用 [Serializable] 属性标记该类,并添加对该接口的支持,并添加隐含构造函数(在该页面中描述,搜索隐含构造函数)。您可以在文本下方的代码中看到其实现示例。
回答by Joe
There used to be an excellent article from Eric Gunnerson on MSDN "The well-tempered exception" but it seems to have been pulled. The URL was:
MSDN上曾经有一篇来自Eric Gunnerson的优秀文章“脾气暴躁的例外”,但似乎已被撤下。网址是:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp08162001.asp
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp08162001.asp
Aydsman's answer is correct, more info here:
Aydsman 的回答是正确的,更多信息在这里:
http://msdn.microsoft.com/en-us/library/ms229064.aspx
http://msdn.microsoft.com/en-us/library/ms229064.aspx
I can't think of any use-case for an Exception with non-serializable members, but if you avoid attempting to serialize/deserialize them in GetObjectData and the deserialization constructor you should be OK. Also mark them with the [NonSerialized] attribute, more as documentation than anything else, since you are implementing the serialization yourself.
我想不出具有不可序列化成员的异常的任何用例,但是如果您避免尝试在 GetObjectData 和反序列化构造函数中序列化/反序列化它们,您应该没问题。还用 [NonSerialized] 属性标记它们,更像是文档而不是其他任何东西,因为您是自己实现序列化的。
回答by David Hill
Mark the class with [Serializable], although I'm not sure how well a IList member will be handled by the serializer.
用 [Serializable] 标记该类,尽管我不确定序列化程序将如何处理 IList 成员。
EDIT
编辑
The post below is correct, because your custom exception has constructor that takes parameters, you must implement ISerializable.
下面的帖子是正确的,因为您的自定义异常具有带参数的构造函数,您必须实现 ISerializable。
If you used a default constructor and exposed the two custom members with getter/setter properties, you could get away with just setting the attribute.
如果您使用默认构造函数并使用 getter/setter 属性公开两个自定义成员,则只需设置属性即可。
回答by Mark Bessey
I have to think that wanting to serialize an exception is a strong indication that you're taking the wrong approach to something. What's the ultimate goal, here? If you're passing the exception between two processes, or between separate runs of the same process, then most of the properties of the exception aren't going to be valid in the other process anyway.
我不得不认为想要序列化异常强烈表明您对某些事情采取了错误的方法。这里的终极目标是什么?如果您在两个进程之间或在同一进程的不同运行之间传递异常,则异常的大多数属性无论如何都不会在另一个进程中有效。
It would probably make more sense to extract the state information you want at the catch() statement, and archive that.
在 catch() 语句中提取您想要的状态信息并将其存档可能更有意义。

