基础实现,没有自定义属性
SerializableExceptionWithoutCustomProperties.cs:
namespace SerializableExceptions
{
using System;
using System.Runtime.Serialization;
[Serializable]
public class SerializableExceptionWithoutCustomProperties : Exception
{
public SerializableExceptionWithoutCustomProperties()
{
}
public SerializableExceptionWithoutCustomProperties(string message)
: base(message)
{
}
public SerializableExceptionWithoutCustomProperties(string message, Exception innerException)
: base(message, innerException)
{
}
protected SerializableExceptionWithoutCustomProperties(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
}
完整实现,包含自定义属性
完整实现一个自定义可序列化异常 (MySerializableException
) 和一个派生的 sealed
异常 (MyDerivedSerializableException
)。
这个实现的主要点总结如下:
- 必须在每个派生类上使用
[Serializable]
属性进行修饰 — 这个属性不会从基类继承,如果没有指定,序列化将会失败并抛出一个 SerializationException
,其中说明 "Type X in Assembly Y is not marked as serializable."
- 必须实现自定义序列化。仅有
[Serializable]
属性是不够的 — Exception
实现了 ISerializable
接口,这意味着你的派生类也必须实现自定义序列化。这包括两个步骤:
- 提供一个序列化构造函数。如果你的类是
sealed
,则此构造函数应为 private
,否则它应该是 protected
,以允许派生类访问。
- 重写 GetObjectData() 并确保在最后调用
base.GetObjectData(info, context)
,以便让基类保存自己的状态。
SerializableExceptionWithCustomProperties.cs:
namespace SerializableExceptions
{
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Security.Permissions;
[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)]
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);
info.AddValue("ValidationErrors", this.ValidationErrors, typeof(IList<string>));
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)]
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."));
string exceptionToString = ex.ToString();
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream())
{
bf.Serialize(ms, ex);
ms.Seek(0, 0);
ex = (SerializableExceptionWithoutCustomProperties)bf.Deserialize(ms);
}
Assert.AreEqual(exceptionToString, ex.ToString(), "ex.ToString()");
}
[TestMethod]
public void TestSerializableExceptionWithCustomProperties()
{
SerializableExceptionWithCustomProperties ex =
new SerializableExceptionWithCustomProperties(Message, ResourceName, validationErrors);
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]");
string exceptionToString = ex.ToString();
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream())
{
bf.Serialize(ms, ex);
ms.Seek(0, 0);
ex = (SerializableExceptionWithCustomProperties)bf.Deserialize(ms);
}
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(exceptionToString, ex.ToString(), "ex.ToString()");
}
[TestMethod]
public void TestDerivedSerializableExceptionWithAdditionalCustomProperty()
{
DerivedSerializableExceptionWithAdditionalCustomProperty ex =
new DerivedSerializableExceptionWithAdditionalCustomProperty(Message, Username, ResourceName, validationErrors);
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);
string exceptionToString = ex.ToString();
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream())
{
bf.Serialize(ms, ex);
ms.Seek(0, 0);
ex = (DerivedSerializableExceptionWithAdditionalCustomProperty)bf.Deserialize(ms);
}
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);
Assert.AreEqual(exceptionToString, ex.ToString(), "ex.ToString()");
}
}
}
GetObjectData
方法从未被调用过,但我可以重写ToString()
方法并使其被调用。 - LP13