使自定义。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; }
}
}

如果这个异常被序列化和反序列化,两个自定义属性(ResourceNameValidationErrors)将不会被保留。属性将返回null

是否有通用的代码模式来实现自定义异常的序列化?

93568 次浏览

用[Serializable]标记类,尽管我不确定序列化器将如何处理IList成员。

编辑

下面的帖子是正确的,因为你的自定义异常有接受参数的构造函数,你必须实现ISerializable。

如果使用默认构造函数,并使用getter/setter属性公开两个自定义成员,则只需设置属性即可。

实现ISerializable,并遵循正常模式来完成此操作。

您需要用[Serializable]属性标记类,并添加对该接口的支持,并添加隐含的构造函数(在该页中描述,搜索隐含构造函数)。您可以在文本下面的代码中看到它的实现示例。

我不得不认为,想要序列化一个异常强烈地表明您对某些事情采取了错误的方法。这里的终极目标是什么?如果在两个进程之间传递异常,或者在同一个进程的不同运行之间传递异常,那么异常的大多数属性在另一个进程中是无效的。

在catch()语句中提取所需的状态信息并将其存档可能更有意义。

异常已经是可序列化的,但是你需要覆盖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>));
}


}

曾经有一篇来自Eric Gunnerson在MSDN上的优秀文章“脾气暴躁的例外”,但它似乎已经被撤下了。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

我想不出带有不可序列化成员的Exception的任何用例,但如果您避免尝试在GetObjectData和反序列化构造函数中序列化/反序列化它们,应该是可以的。还要用[NonSerialized]属性标记它们,这更像是文档,因为您自己实现了序列化。

基本实现,没有自定义属性

< em > SerializableExceptionWithoutCustomProperties.cs: < / em >

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)的完整实现。

关于这个实现的要点总结如下:

  1. You 必须用[Serializable]属性来装饰每个派生类吗 - 此属性不是继承自基类,如果未指定该属性,序列化将失败,并返回SerializationException声明“程序集Y中的类型X未标记为可序列化。”
  2. <李>你必须实现自定义序列化吗。只有[Serializable]属性是不够的——Exception实现了ISerializable,这意味着你的派生类还必须实现自定义序列化。这包括两个步骤:
    1. 提供一个序列化构造函数。如果你的类是sealed,这个构造函数应该是private,否则它应该是protected以允许访问派生类。
    2. 覆盖GetObjectData (),并确保在结束时调用到base.GetObjectData(info, context),以便让基类保存自己的状态。
    3. 李< / ol > < / >

    < em > SerializableExceptionWithCustomProperties.cs: < / em >

    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);
    }
    }
    }
    

    < em > DerivedSerializableExceptionWithAdditionalCustomProperties.cs: < / em >

    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单元测试上面定义的三种异常类型。

    < em > UnitTests.cs: < / em >

    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()");
    }
    }
    }
    

为了补充上面的正确答案,我发现如果我将自定义属性存储在Exception类的Data集合中,我可以避免做这种自定义序列化的事情。

例如:

[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; }
}


// ...
}

就性能而言,这可能比丹尼尔提供的解决方案更低效,可能只适用于“整型”类型,如字符串和整数等。

尽管如此,这对我来说还是很容易理解的。

在. net Core中,. net 5.0及以上版本不使用Serializable,因为微软遵循BinaryFormatter中发现的安全威胁实践。

使用存储在数据集合中的示例