问题描述
更具体地,当异常包含其可以或可以不本身可序列化。定制对象
拿这个例子:
公共类MyException:异常
{
私人只读字符串资源名称;
私人只读的IList<字符串> validationErrors;
公共MyException(字符串资源名称,IList的<字符串> validationErrors)
{
this.resourceName =资源名称;
this.validationErrors = validationErrors;
}
公共字符串资源名称
{
{返回this.resourceName; }
}
公众的IList<字符串> ValidationErrors
{
{返回this.validationErrors; }
}
}
如果该异常序列化和反序列化,这两个自定义属性(<$ C C $>资源名称和 ValidationErrors
)不会是preserved。该属性将返回空
。
是否有实现序列化自定义异常的共同code模式?
基本实现无自定义属性
SerializableExceptionWithoutCustomProperties.cs:的
命名空间SerializableExceptions
{
使用系统;
使用System.Runtime.Serialization;
[可序列化]
//重要说明:此属性是不是从Exception继承的,必须指定
//否则序列化将失败,并SerializationException说明
//,在大会Y型X没有标记为可序列化。
公共类SerializableExceptionWithoutCustomProperties:异常
{
公共SerializableExceptionWithoutCustomProperties()
{
}
公共SerializableExceptionWithoutCustomProperties(字符串消息)
:碱(消息)
{
}
公共SerializableExceptionWithoutCustomProperties(字符串消息,异常的InnerException)
:基地(消息的InnerException)
{
}
//如果没有这个构造函数,反序列化将失败
保护SerializableExceptionWithoutCustomProperties(SerializationInfo中的信息,的StreamingContext上下文)
:基地(信息,上下文)
{
}
}
}
全面实施,具有自定义属性
完全实现自定义序列化异常( MySerializableException
),并派生密封
异常( MyDerivedSerializableException
)。
这个实施要点总结如下:
- 您的必须装点每个派生类与
[Serializable接口]
属性 -此属性不是从基类继承,如果没有指定,序列化将失败,一个SerializationException
指出的,在大会Y型X没有标记为可序列化。的 - 您的必须实现自定义序列化。该
[Serializable接口]
属性是不够的 -异常
工具ISerializable的
这意味着你的派生类还必须实现自定义序列化。这包括两个步骤:- 提供一个序列化构造。此构造应
私人
如果你的类是密封
,否则应保护
允许访问派生类。 - 覆盖GetObjectData使用()并确保你通过打电话到
base.GetObjectData(信息,上下文)
末,为了让基础类保存自己的状态。
- 提供一个序列化构造。此构造应
SerializableExceptionWithCustomProperties.cs:的
命名空间SerializableExceptions
{
使用系统;
使用System.Collections.Generic;
使用System.Runtime.Serialization;
使用System.Security.Permissions;
[可序列化]
//重要说明:此属性是不是从Exception继承的,必须指定
//否则序列化将失败,并SerializationException说明
//,在大会Y型X没有标记为可序列化。
公共类SerializableExceptionWithCustomProperties:异常
{
私人只读字符串资源名称;
私人只读的IList&LT;字符串&GT; validationErrors;
公共SerializableExceptionWithCustomProperties()
{
}
公共SerializableExceptionWithCustomProperties(字符串消息)
:碱(消息)
{
}
公共SerializableExceptionWithCustomProperties(字符串消息,异常的InnerException)
:基地(消息的InnerException)
{
}
公共SerializableExceptionWithCustomProperties(字符串消息,串资源名称,IList的&LT;字符串&GT; validationErrors)
:碱(消息)
{
this.resourceName =资源名称;
this.validationErrors = validationErrors;
}
公共SerializableExceptionWithCustomProperties(字符串消息,串资源名称,IList的&LT;字符串&GT; validationErrors,异常的InnerException)
:基地(消息的InnerException)
{
this.resourceName =资源名称;
this.validationErrors = validationErrors;
}
[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter =真)
//构造应该被保护的非密封类,私人的密封类。
//(串行器调用此构造函数通过反射,因此它可以是私有的)
保护SerializableExceptionWithCustomProperties(SerializationInfo中的信息,的StreamingContext上下文)
:基地(信息,上下文)
{
this.resourceName = info.GetString(资源名称);
this.validationErrors =(IList的&LT;字符串&GT;)info.GetValue(ValidationErrors的typeof(IList的&LT;字符串&GT;));
}
公共字符串资源名称
{
{返回this.resourceName; }
}
公众的IList&LT;字符串&GT; ValidationErrors
{
{返回this.validationErrors; }
}
[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter =真)
公众覆盖无效GetObjectData使用(SerializationInfo中的信息,的StreamingContext上下文)
{
如果(资讯== NULL)
{
抛出新ArgumentNullException(信息);
}
info.AddValue(资源名称,this.ResourceName);
//注意:如果名单,其中,T&gt;中是不是序列化的,你可能需要解决的另一个
//加入列表的方法,这是作秀?
info.AddValue(ValidationErrors,this.ValidationErrors的typeof(IList的&LT;字符串&GT;));
//必须调用通过对基类让它保存自己的状态
base.GetObjectData(信息,上下文);
}
}
}
DerivedSerializableExceptionWithAdditionalCustomProperties.cs:的
命名空间SerializableExceptions
{
使用系统;
使用System.Collections.Generic;
使用System.Runtime.Serialization;
使用System.Security.Permissions;
[可序列化]
公共密封类DerivedSerializableExceptionWithAdditionalCustomProperty:SerializableExceptionWithCustomProperties
{
私人只读字符串用户名;
公共DerivedSerializableExceptionWithAdditionalCustomProperty()
{
}
公共DerivedSerializableExceptionWithAdditionalCustomProperty(字符串消息)
:碱(消息)
{
}
公共DerivedSerializableExceptionWithAdditionalCustomProperty(字符串消息,异常的InnerException)
:基地(消息的InnerException)
{
}
公共DerivedSerializableExceptionWithAdditionalCustomProperty(字符串消息,用户名字符串,字符串资源名称,IList的&LT;字符串&GT; validationErrors)
:基地(信息,资源名称,validationErrors)
{
this.username =用户名;
}
公共DerivedSerializableExceptionWithAdditionalCustomProperty(字符串消息,用户名字符串,字符串资源名称,IList的&LT;字符串&GT; validationErrors,异常的InnerException)
:基地(信息,资源名称,validationErrors,的InnerException)
{
this.username =用户名;
}
[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter =真)
//序列化的构造函数是私有的,因为这个类是密封
私人DerivedSerializableExceptionWithAdditionalCustomProperty(SerializationInfo中的信息,的StreamingContext上下文)
:基地(信息,上下文)
{
this.username = info.GetString(用户名);
}
公共字符串用户名
{
{返回this.username; }
}
公众覆盖无效GetObjectData使用(SerializationInfo中的信息,的StreamingContext上下文)
{
如果(资讯== NULL)
{
抛出新ArgumentNullException(信息);
}
info.AddValue(用户名,this.username);
base.GetObjectData(信息,上下文);
}
}
}
单元测试
MSTest的单元测试三个异常类型上述定义。
UnitTests.cs:的
命名空间SerializableExceptions
{
使用系统;
使用System.Collections.Generic;
使用System.IO;
使用System.Runtime.Serialization.Formatters.Binary;
使用Microsoft.VisualStudio.TestTools.UnitTesting;
[识别TestClass]
公共类单元测试
{
私人常量字符串消息=小部件已经不可避免blooped了。
私人常量字符串资源名称=资源A;
私人常量字符串ValidationError1 =你忘了设置高手帮的标志。
私人常量字符串ValidationError2 =沃利无法在零重力操作。
私人只读表&LT;字符串&GT; validationErrors =新的名单,其中,串&GT;();
私人常量字符串用户名=巴里;
公共单元测试()
{
validationErrors.Add(ValidationError1);
validationErrors.Add(ValidationError2);
}
[测试方法]
公共无效TestSerializableExceptionWithoutCustomProperties()
{
例外EX =
新SerializableExceptionWithoutCustomProperties(
消息,新的异常(内部异常。));
//保存完整的ToString()值,其中包括异常消息和堆栈跟踪。
字符串exceptionToString = ex.ToString();
//往返异常:序列化并用的BinaryFormatter反序列化
BinaryFormatter的BF =新的BinaryFormatter();
使用(MemoryStream的毫秒=新的MemoryStream())
{
//保存对象状态
bf.Serialize(MS,EX);
//重新使用相同的流为反序列
ms.Seek(0,0);
//替换为反序列化的一个原始异常
离=(SerializableExceptionWithoutCustomProperties)bf.Deserialize(毫秒);
}
//仔细检查异常消息和堆栈跟踪(由基异常独资)是preserved
Assert.AreEqual(exceptionToString,ex.ToString(),ex.ToString());
}
[测试方法]
公共无效TestSerializableExceptionWithCustomProperties()
{
SerializableExceptionWithCustomProperties EX =
新SerializableExceptionWithCustomProperties(信息,资源名称,validationErrors);
//完整性检查:确保自定义属性序列化之前设置
Assert.AreEqual(消息,ex.Message,消息);
Assert.AreEqual(资源名称,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]);
//保存完整的ToString()值,其中包括异常消息和堆栈跟踪。
字符串exceptionToString = ex.ToString();
//往返异常:序列化并用的BinaryFormatter反序列化
BinaryFormatter的BF =新的BinaryFormatter();
使用(MemoryStream的毫秒=新的MemoryStream())
{
//保存对象状态
bf.Serialize(MS,EX);
//重新使用相同的流为反序列
ms.Seek(0,0);
//替换为反序列化的一个原始异常
离=(SerializableExceptionWithCustomProperties)bf.Deserialize(毫秒);
}
//确保自定义属性序列化之后pserved $ P $
Assert.AreEqual(消息,ex.Message,消息);
Assert.AreEqual(资源名称,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]);
//仔细检查异常消息和堆栈跟踪(由基异常独资)是preserved
Assert.AreEqual(exceptionToString,ex.ToString(),ex.ToString());
}
[测试方法]
公共无效TestDerivedSerializableExceptionWithAdditionalCustomProperty()
{
DerivedSerializableExceptionWithAdditionalCustomProperty EX =
新DerivedSerializableExceptionWithAdditionalCustomProperty(信息,用户名,资源名称,validationErrors);
//完整性检查:确保自定义属性序列化之前设置
Assert.AreEqual(消息,ex.Message,消息);
Assert.AreEqual(资源名称,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(用户名,ex.Username);
//保存完整的ToString()值,其中包括异常消息和堆栈跟踪。
字符串exceptionToString = ex.ToString();
//往返异常:序列化并用的BinaryFormatter反序列化
BinaryFormatter的BF =新的BinaryFormatter();
使用(MemoryStream的毫秒=新的MemoryStream())
{
//保存对象状态
bf.Serialize(MS,EX);
//重新使用相同的流为反序列
ms.Seek(0,0);
//替换为反序列化的一个原始异常
离=(DerivedSerializableExceptionWithAdditionalCustomProperty)bf.Deserialize(毫秒);
}
//确保自定义属性序列化之后pserved $ P $
Assert.AreEqual(消息,ex.Message,消息);
Assert.AreEqual(资源名称,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(用户名,ex.Username);
//仔细检查异常消息和堆栈跟踪(由基异常独资)是preserved
Assert.AreEqual(exceptionToString,ex.ToString(),ex.ToString());
}
}
}
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 (ResourceName
and ValidationErrors
) will not be preserved. The properties will return null
.
Is there a common code pattern for implementing serialization for custom exception?
Base implementation, without custom properties
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 sealed
exception (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 aSerializationException
stating that "Type X in Assembly Y is not marked as serializable." - You must implement custom serialization. The
[Serializable]
attribute alone is not enough —Exception
implementsISerializable
which means your derived classes must also implement custom serialization. This involves two steps:- Provide a serialization constructor. This constructor should be
private
if your class issealed
, otherwise it should beprotected
to 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
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);
}
}
}
Unit Tests
MSTest unit tests for the three exception types defined above.
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()");
}
}
}
这篇关于什么是正确的方法,使一个自定义的.NET异常序列化?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!