Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <h2>Base implementation, without custom properties</h2> <p><strong><em>SerializableExceptionWithoutCustomProperties.cs:</em></strong></p> <pre><code>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) { } } } </code></pre> <h2>Full implementation, with custom properties</h2> <p>Complete implementation of a custom serializable exception (<code>MySerializableException</code>), and a derived <code>sealed</code> exception (<code>MyDerivedSerializableException</code>).</p> <p>The main points about this implementation are summarized here:</p> <ol> <li>You <strong>must decorate each derived class with the <code>[Serializable]</code> attribute</strong> — This attribute is not inherited from the base class, and if it is not specified, serialization will fail with a <code>SerializationException</code> stating that <em>"Type X in Assembly Y is not marked as serializable."</em></li> <li>You <strong>must implement custom serialization</strong>. The <code>[Serializable]</code> attribute alone is not enough — <code>Exception</code> implements <code>ISerializable</code> which means your derived classes must also implement custom serialization. This involves two steps: <ol> <li><strong>Provide a serialization constructor</strong>. This constructor should be <code>private</code> if your class is <code>sealed</code>, otherwise it should be <code>protected</code> to allow access to derived classes.</li> <li><strong>Override GetObjectData()</strong> and make sure you call through to <code>base.GetObjectData(info, context)</code> at the end, in order to let the base class save its own state.</li> </ol></li> </ol> <p><strong><em>SerializableExceptionWithCustomProperties.cs:</em></strong></p> <pre><code>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&lt;string&gt; 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&lt;string&gt; validationErrors) : base(message) { this.resourceName = resourceName; this.validationErrors = validationErrors; } public SerializableExceptionWithCustomProperties(string message, string resourceName, IList&lt;string&gt; 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&lt;string&gt;)info.GetValue("ValidationErrors", typeof(IList&lt;string&gt;)); } public string ResourceName { get { return this.resourceName; } } public IList&lt;string&gt; 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&lt;T&gt;" 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&lt;string&gt;)); // MUST call through to the base class to let it save its own state base.GetObjectData(info, context); } } } </code></pre> <p><strong><em>DerivedSerializableExceptionWithAdditionalCustomProperties.cs:</em></strong></p> <pre><code>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&lt;string&gt; validationErrors) : base(message, resourceName, validationErrors) { this.username = username; } public DerivedSerializableExceptionWithAdditionalCustomProperty(string message, string username, string resourceName, IList&lt;string&gt; 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); } } } </code></pre> <hr> <h2>Unit Tests</h2> <p>MSTest unit tests for the three exception types defined above.</p> <p><strong><em>UnitTests.cs:</em></strong></p> <pre><code>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&lt;string&gt; validationErrors = new List&lt;string&gt;(); 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()"); } } } </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload