더 구체적으로, 예외에 직렬화 가능하거나 직렬화 불가능한 사용자 정의 객체가 포함 된 경우.
이 예제를 보자 :
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
됩니다.
사용자 정의 예외에 대한 직렬화를 구현하기위한 공통 코드 패턴이 있습니까?
답변
사용자 정의 속성이없는 기본 구현
CustomProperties.cs가없는 SerializableException :
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가 직렬화로 표시되지 않습니다.” - 당신은 사용자 정의 직렬화를 구현해야합니다 .
[Serializable]
혼자 속성은 충분하지 않다 –Exception
구현하는ISerializable
파생 클래스는 사용자 정의 직렬화를 구현해야 함을 의미한다. 여기에는 두 단계가 포함됩니다.- 직렬화 생성자를 제공합니다 . 이 생성자는
private
클래스가sealed
인 경우, 그렇지 않은 경우protected
파생 클래스에 대한 액세스를 허용 해야합니다 . base.GetObjectData(info, context)
기본 클래스가 자체 상태를 저장하게하려면 GetObjectData ()를 재정의 하고 마지막에 호출해야합니다 .
- 직렬화 생성자를 제공합니다 . 이 생성자는
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);
}
}
}
AdditionalCustomProperties.cs로 DerivedSerializableException :
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()");
}
}
}
답변
예외는 이미 직렬화 가능하지만 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>));
}
}
답변
ISerializable을 구현 하고이를위한 일반적인 패턴 을 따르십시오 .
클래스에 [Serializable] 속성으로 태그를 지정하고 해당 인터페이스에 대한 지원을 추가하고 내재 된 생성자를 추가해야합니다 (해당 페이지에 설명 된 검색 은 생성자 를 암시 함 ). 텍스트 아래 코드에서 구현 예제를 볼 수 있습니다.
답변
위의 정답에 추가하기 위해 클래스 Data
컬렉션 에 사용자 정의 속성을 저장하면이 사용자 정의 직렬화 작업을 피할 수 있음을 발견 했습니다Exception
.
예 :
[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; }
}
// ...
}
아마도 이것은 Daniel이 제공하는 솔루션 보다 성능면에서 비효율적이며 문자열 및 정수 등과 같은 “통합”유형에서만 작동합니다.
여전히 그것은 나에게 매우 쉽고 이해하기 쉬웠습니다.
답변
에릭 건너 슨 (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
직렬화 할 수없는 멤버의 예외에 대한 유스 케이스는 생각할 수 없지만 GetObjectData 및 직렬화 해제 생성자에서 직렬화 / 직렬화 해제를 시도하지 않으면 괜찮을 것입니다. 또한 직렬화를 직접 구현하고 있으므로 [NonSerialized] 속성을 다른 것보다 문서로 표시하십시오.
답변
직렬화 기가 IList 멤버를 얼마나 잘 처리하는지 잘 모르겠지만 클래스를 [Serializable]로 표시하십시오.
편집하다
사용자 지정 예외에 매개 변수를 사용하는 생성자가 있으므로 아래 게시물이 정확합니다. ISerializable을 구현해야합니다.
기본 생성자를 사용하고 getter / setter 속성이있는 두 개의 사용자 지정 멤버를 노출 한 경우 속성을 설정하기 만하면 벗어날 수 있습니다.
답변
예외를 직렬화하려는 것이 잘못된 접근 방식을 취하고 있다는 강력한 표시라고 생각해야합니다. 여기서 궁극적 인 목표는 무엇입니까? 두 프로세스 간 또는 동일한 프로세스의 개별 실행간에 예외를 전달하는 경우 예외의 대부분의 속성은 다른 프로세스에서 유효하지 않습니다.
catch () 문에서 원하는 상태 정보를 추출하여 보관하는 것이 더 합리적 일 것입니다.