Daniel Fortunov


 

 Daniel Fortunov's Adventures in Software Development » Serializable Exceptions with custom properties

 0 Comments- Add comment | Back to Software Development Blog Written on 10-Feb-2009 by asqui

If you define your own .NET Exception type (derived from System.Exception) it is important to remember that this custom exception type will not be serializable by default.

It is often necessary for Exceptions be serializable because without this they will not travel across remoting boundaries, or even between application domains within the same process.

Rule 1: Custom exception types must be marked with the [Serializable] attribute

It is the [Serializable] attribute that indicates if your type is serializable. Although System.Exception is decorated with the [Serializable] attribute, this attribute will not be inherited by your custom exception class. The reason for this is clear when we open up Reflector and look at the declaration of SerializableAttribute:

[ComVisible(true)]
[AttributeUsage(
AttributeTargets.Delegate | AttributeTargets.Enum |
AttributeTargets.Struct | AttributeTargets.Class,
Inherited=false)] // Not inherited!
public sealed class SerializableAttribute : Attribute
{ ... }

This means that any custom exception types you create must be explicitly decorated with the [Serializable] attribute.

Rule 2: Custom exception types must implement ISerializable

The default serialization behaviour (which you get for free when you just decorate your class with [Serializable]) is to serialize all public and private fields in the type. This is good enough for many cases, but System.Exception needs to do some special things when being serialized and de-serialized. (For example, populate the lazily instantiated stack trace, which may not have been generated if the default serializer tries to just reach in and grab the field values through reflection.)

Because System.Exception implements the ISerializable interface, by inheritance, your derived exception class also necessarily implements this interface. This means you cannot rely on any default serialization behaviour.

You have to play along with the full ISerializable pattern. This involves two things:

  1. Override the GetObjectData() method, which gives your object the opportunity to save its state into a SerializationInfo object (and then call through to the base class to let it do the same!)
  2. Override the de-serialization constructor, which gives your object the opportunity to initialize itself by retrieving its state from a SerializationInfo object (and then call through to the base class to let it do the same!)

Other caveats and notes

  • Your de-serialization constructor should be protected for unsealed classes, or private for sealed classes. (The Serializer uses reflection to invoke your de-serialization constructor, so it can manage even if the constructor is not publically accessible.)
  • Remember to specify the appropriate security demands for your de-serialization constructor, and GetObjectData() method. This will ensure that code running with lower privileges will not be able to, for example, invoke GetObjectData() to covertly extract internal state from your class.

Example

Here is an example custom exception which defines additional properties and serializes them correctly.

[Serializable] 
// Attribute is NOT inherited from Exception and MUST be specified.
public class SerializableException : Exception
{
private readonly string resourceName;
private readonly IList<string> validationErrors;

public SerializableException()
{
}

public SerializableException(string message)
: base(message)
{
}

public SerializableException(string message, Exception inner)
: base(message, inner)
{
}

public SerializableException(string message, string resourceName, IList<string> validationErrors)
: base(message)
{
this.resourceName = resourceName;
this.validationErrors = validationErrors;
}

public SerializableException(string message, string resourceName, IList<string> validationErrors, Exception inner)
: base(message, inner)
{
this.resourceName = resourceName;
this.validationErrors = validationErrors;
}

[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
// Protected for unsealed classes, private for sealed.
protected SerializableException(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);
}
}

You can also download a VS2008 project with a more complete set of examples, including unit tests.

(This post was based on my Stack Overflow question, “What is the correct way to make a custom .NET Exception serializable?”)

Send to a friend

Comments

  • There are currently no comments for this post

Leave a Comment









Loading …
  • Server: web1.webjam.com
  • Total queries:
  • Serialization time: 188ms
  • Execution time: 219ms
  • XSLT time: $$$XSLT$$$ms