Ova

How to Use XmlIncludeAttribute for XML Serialization?

Published in XML Serialization 5 mins read

The XmlIncludeAttribute is a crucial tool in C# for enabling the XmlSerializer to correctly serialize and deserialize objects that involve polymorphism. It allows you to inform the serializer about derived types that might appear where a base type is expected, ensuring proper data handling.

Understanding XmlIncludeAttribute

When you're serializing objects into XML, especially in scenarios where a property or field of a base class type might actually hold an instance of a derived class, the XmlSerializer needs to be explicitly told about these derived types. Without this information, the serializer wouldn't know how to handle the specific properties of the derived class, potentially leading to errors or incomplete serialization.

The XmlIncludeAttribute acts as a directive, telling the XmlSerializer that when it encounters an object of the base type, it should be prepared to recognize and correctly process instances of the specified derived types. This ensures that all data, including the unique properties of derived classes, is preserved during the serialization process and accurately reconstructed during deserialization.

When and Where to Apply It

You should use the XmlIncludeAttribute when you plan to call the Serialize or Deserialize methods of the XmlSerializer class and your object model contains polymorphism.

Here’s how to apply it effectively:

  1. On the Base Class (or Interface): Apply the XmlIncludeAttribute to the definition of the base class (or interface) that declares the polymorphic member. This is the class from which other types derive.
  2. Specify Derived Type: As an argument to the attribute, you must specify the Type of the derived class that the XmlSerializer should recognize. For example, [XmlInclude(typeof(DerivedClass))].
  3. Multiple Derived Types: If your base class can have multiple different derived types, you must apply a separate XmlIncludeAttribute for each derived type you want the XmlSerializer to handle.

By applying this attribute, you equip the XmlSerializer with the necessary metadata to process both the base and derived object types accurately, preventing common serialization errors related to type mismatch.

Practical Example: Polymorphic XML Serialization

Let's illustrate how to use XmlIncludeAttribute with a common scenario involving a base Shape class and its derived Circle and Rectangle classes.

using System;
using System.Xml.Serialization;
using System.IO;

// 1. Define the base class and apply XmlIncludeAttribute for each derived type
[XmlInclude(typeof(Circle))]
[XmlInclude(typeof(Rectangle))]
public abstract class Shape
{
    public string Name { get; set; }
    public abstract double Area(); // Example polymorphic method
}

public class Circle : Shape
{
    public double Radius { get; set; }
    public Circle() { Name = "Circle"; }
    public override double Area() => Math.PI * Radius * Radius;
}

public class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }
    public Rectangle() { Name = "Rectangle"; }
    public override double Area() => Width * Height;
}

// A container class that holds an array of base type (Shape)
public class Drawing
{
    public Shape[] Shapes { get; set; }
    public Drawing() { Shapes = new Shape[0]; }
}

public class Program
{
    public static void Main(string[] args)
    {
        // Create an object graph with derived types
        Drawing myDrawing = new Drawing
        {
            Shapes = new Shape[]
            {
                new Circle { Radius = 5.0 },
                new Rectangle { Width = 10.0, Height = 4.0 }
            }
        };

        XmlSerializer serializer = new XmlSerializer(typeof(Drawing));
        string filePath = "drawing.xml";

        // Serialize the object graph to an XML file
        Console.WriteLine("--- Serializing Drawing ---");
        using (FileStream fs = new FileStream(filePath, FileMode.Create))
        {
            serializer.Serialize(fs, myDrawing);
        }
        Console.WriteLine($"Serialized to {filePath}:\n");
        Console.WriteLine(File.ReadAllText(filePath));

        // Deserialize the XML file back into objects
        Console.WriteLine("\n--- Deserializing Drawing ---");
        Drawing loadedDrawing;
        using (FileStream fs = new FileStream(filePath, FileMode.Open))
        {
            loadedDrawing = (Drawing)serializer.Deserialize(fs);
        }
        Console.WriteLine("Deserialized Drawing content:");
        foreach (var shape in loadedDrawing.Shapes)
        {
            Console.WriteLine($"- {shape.Name}");
            // Use 'is' and 'as' to check and cast to the correct derived type
            if (shape is Circle circle)
            {
                Console.WriteLine($"  Type: Circle, Radius: {circle.Radius}, Area: {circle.Area():F2}");
            }
            else if (shape is Rectangle rectangle)
            {
                Console.WriteLine($"  Type: Rectangle, Width: {rectangle.Width}, Height: {rectangle.Height}, Area: {rectangle.Area():F2}");
            }
        }
    }
}

Output XML Example (drawing.xml):

<?xml version="1.0"?>
<Drawing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Shapes>
    <Shape xsi:type="Circle">
      <Name>Circle</Name>
      <Radius>5</Radius>
    </Shape>
    <Shape xsi:type="Rectangle">
      <Name>Rectangle</Name>
      <Width>10</Width>
      <Height>4</Height>
    </Shape>
  </Shapes>
</Drawing>

Explanation of the Example:

  • The Shape class, which is the base for Circle and Rectangle, is decorated with [XmlInclude(typeof(Circle))] and [XmlInclude(typeof(Rectangle))].
  • This decoration tells the XmlSerializer that when it encounters a Shape object within the Drawing.Shapes array, it should look out for and be able to serialize/deserialize instances of Circle or Rectangle.
  • During serialization, the XmlSerializer automatically adds an xsi:type attribute to the XML elements for the derived types (<Shape xsi:type="Circle">). This attribute explicitly states the actual type of the object.
  • During deserialization, the XmlSerializer reads this xsi:type attribute and uses it to correctly instantiate Circle and Rectangle objects, preserving their specific data (Radius, Width, Height).

Benefits of Using XmlIncludeAttribute

Leveraging XmlIncludeAttribute offers several key advantages for robust XML serialization:

  • Polymorphic Serialization: It is the primary mechanism for enabling XmlSerializer to handle derived types referenced by base types, which is fundamental for flexible object models.
  • Data Integrity: Guarantees that all properties, including those unique to derived classes, are correctly serialized into the XML output and accurately restored during deserialization.
  • Type Recognition: Empowers the XmlSerializer to recognize the precise derived object type from the XML during deserialization, ensuring your application works with the correct object instances.
  • Error Prevention: Prevents InvalidOperationException that would otherwise occur when the XmlSerializer encounters an unknown derived type in a polymorphic context.

Key Considerations

  • XmlSerializer Specific: This attribute is exclusively for use with the System.Xml.Serialization.XmlSerializer. Other .NET serialization frameworks, such as DataContractSerializer, use different mechanisms (KnownTypeAttribute).
  • Explicit Declaration: You must explicitly declare all possible derived types that might appear in your object graph for XmlSerializer to correctly handle them. If a derived type is not included, it will not be properly serialized or deserialized.
  • Performance: While generally negligible, for an extremely large number of derived types, declaring them all might have a minor impact on the initial creation time of the XmlSerializer instance.

By understanding and correctly applying XmlIncludeAttribute, you can effectively manage complex object hierarchies and ensure reliable XML serialization and deserialization in your .NET applications.