# Implementing a Serializer Class

Learn how to implement a Serializer class and utilize its features for efficient serialization and deserialization of objects in your .NET applications.

## Content

When using the [Serializer](/spreadnet/api/latest/online-win/FarPoint.Win/FarPoint.Win.Serializer.html) class, remember these tips:

* You must have a parameterless constructor defined in order for Serializer.[CreateObjectInstanceAndDeserialize](/spreadnet/api/latest/online-win/FarPoint.Win/FarPoint.Win.Serializer.CreateObjectInstanceAndDeserialize.html) to create a new instance of your class to deserialize.
* It is a good idea to initialize the fields in the class with some default or dummy values, but it is not necessary if you always save each field to the XML regardless of whether it is set to its default (which is required to make [GetObjectXml](/spreadnet/api/latest/online-win/FarPoint.Win/FarPoint.Win.Serializer.GetObjectXml.html) and [SetObjectXml](/spreadnet/api/latest/online-win/FarPoint.Win/FarPoint.Win.Serializer.SetObjectXml.html) work properly).
* The **Serializer** [CanSerializeObject](/spreadnet/api/latest/online-win/FarPoint.Win/FarPoint.Win.Serializer.CanSerializeObject.html) method determines whether the specified object can be serialized with **SerializeObject**. This method checks whether the object implements **ICanSerializeXml** (in which case it returns the value of the object's **CanSerializeXml** property), or whether the object implements **ISerializeSupport**, or is a simple type, or if the type's **IsSerializable** property returns true.
* Saving the object using elements (see the first subclass example below) requires more coding, but results in more readable XML when the number of properties being saved is high (as in this example of the **LineBorder** class below).
* Saving the object using attributes (the second subclass example below) requires less coding, and results in more readable XML when the number of properties being saved is low (as in this example of the **GradientLineBorder** class below), so the latter subclass example is better.
* If you implement [ISerializeSupport](/spreadnet/api/latest/online-win/FarPoint.Win/FarPoint.Win.ISerializeSupport.html) using elements, then you should call the base class implementation first (if there is one), then save and load your properties. You should also return after reading the last end element that you serialize, so that subclasses can also use elements to serialize their properties.
* If you implement [ISerializeSupport](/spreadnet/api/latest/online-win/FarPoint.Win/FarPoint.Win.ISerializeSupport.html) using attributes, then you should read your attributes first, then call the base class implementation (if there is one).
* You can use both attributes and elements to serialize your object; there are some Spread objects (mostly the models) which do this. In that case you should do the attributes first, then call the base class, then save and load your elements, returning from **Deserialize** after you reach the last end element.

The examples below show saving simple things like integers and colors, but saving other types is no different. There are methods in **Serializer** for saving and loading Color, DateTime, DateTimeFormatInfo, Enum types, Font, Image, Int32 array, NumberFormatInfo, PointF array, String array, and Object. It is best (though not required) to use the most specific method available. For example, it would work to use **SerializeObject** to save a simple type or a Color value, but it is less efficient and may not result in the same XML.
In general, there is a reason each of the methods was created for its specific type. **DateTimeFormatInfo** and **NumberFormatInfo** must be saved using their respective methods because **SerializeObject** would default to using binary serialization, which does not work across versions of the framework (so a **DateTimeFormatInfo** saved using .NET 1.0 does not load using .NET 1.1 or 2.0).
For simple types (Int32, String, Boolean, etc.) it is best to use **ToString**() to convert them to a string for saving (passing **CultureInfo.InvariantCulture** if possible) and then use **XmlConvert** to convert the strings back into values.
**SaveObject** is intended for saving objects which implement [ISerializeSupport](/spreadnet/api/latest/online-win/FarPoint.Win/FarPoint.Win.ISerializeSupport.html), or for certain specific types (intrinsic data types and DataSet), or for generic objects which are serializable but do not implement **ISerializeSupport** (such objects get saved using **EncodeObject**, which uses a **BinaryFormatter** to save the object to a **MemoryStream** and then uses **Convert** to encode the bytes into a base-64 string).

## Debug versus Release Build

To get files saved with a debug build to load into a release build that is strong named, you must eliminate or update the assembly attribute in the node where the object is saved. **SerializeObject** will write an assembly attribute if the assembly in which the object's type is defined is different from the calling assembly and different from the executing assembly (FarPoint.Win.dll).
The calling assembly is usually FarPoint.Win.Spread.dll but is possibly the user's assembly if they are defining their own objects which implement **ISerializeSupport** and saving child objects using **SerializeObject**. The tricky case is, unfortunately, likely the most common one: a user creates a custom object to plug into the Spread's object model (for example, a custom data model) and they want to save and load the Spread using the save and load methods in **FpSpread** and use different builds of their assembly.
In that case, the assembly attribute is required to load the correct assembly containing the object's type definition, but the correct attribute for the debug and release builds is different. In that case, the user would need to edit the value in the XML to change the assembly reference (using something like **String.Replace**).
The less common cases involve users who are using **ISerializeSupport** and **Serializer** to save and load their own files containing objects that they define, perhaps in the same assembly or in a different assembly that they are writing in parallel. If the objects are defined in the same assembly, then the assembly tab will not be written out, and if it is a different assembly, the developer can call the overloads for **SerializeObject** and **CreateObjectInstanceAndDeserialize** which take the **callingAssembly** argument and pass the assembly containing the object (even if that is not really the assembly calling into **Serializer**). That will prevent the assembly attribute from being written out, but it is important that this be done on both ends (the call to save and load) so that the type reference can be resolved and an instance of the object created.
Here are some examples of how you might implement **ISerializeSupport**.

## Using Code

This example uses the base class.
**Example**
The following code provides the implementation of **ISerializeSupport** in **FarPoint.Win.LineBorder**:

```csharp
// A constructor with no arguments is required for ISerializeSupport.
// It does not need to be public though.
    internal LineBorder()
    {
        color = SystemColors.WindowFrame;
        thickness = 1;
        left = top = right = bottom = true;
        inset = new Inset(1);
    }
  /// <p>DOC-SUMMARY-TAG-OPEN</p>
/// Saves the object to XML.
/// <p>DOC-SUMMARY-TAG-CLOSE</p>
/// <param name="w">XmlTextWriter object to which to save the object</param>
/// <returns>true if successful; false otherwise</returns>
    public virtual bool Serialize(XmlTextWriter w)
    {
        w.WriteStartElement("Inset");
        w.WriteElementString("Bottom",inset.Bottom.ToString());
        w.WriteElementString("Left", inset.Left.ToString());
        w.WriteElementString("Right", inset.Right.ToString());
        w.WriteElementString("Top", inset.Top.ToString());
        w.WriteEndElement();
        Serializer.SerializeColor(color, "Color", w);
        w.WriteStartElement("Sides");
        w.WriteElementString("Bottom", bottom.ToString());
        w.WriteElementString("Left", left.ToString());
        w.WriteElementString("Right", right.ToString());
        w.WriteElementString("Top", top.ToString());
        w.WriteEndElement();
        w.WriteElementString("Thickness", thickness.ToString(CultureInfo.InvariantCulture));
        return true;
    }
  
/// <p>DOC-SUMMARY-TAG-OPEN</p>
/// Loads the object from XML.
/// <p>DOC-SUMMARY-TAG-CLOSE</p>
/// <param name="r">XmlNodeReader from which to load the object</param>
/// <returns>true if successful; false otherwise</returns>
    public virtual bool Deserialize(XmlNodeReader r)
    {
        bool inInset = false;
        bool inBottom = false;
        bool inLeft = false;
        bool inRight = false;
        bool inTop = false;
        bool inColor = false;
        bool inSides = false;
        bool inThickness = false;
        int bottom = inset.Bottom;
        int left = inset.Left;
        int right = inset.Right;
          if( r.IsEmptyElement )
            return true;
        while( r.Read() )
        {
            switch( r.NodeType )
            {
            case XmlNodeType.Element:
            if( r.Name.Equals("Inset") )
               inInset = true;
            else if( r.Name.Equals("Color") )
               inColor = true;
            else if( r.Name.Equals("Sides") )
               inSides = true;
            else if( inInset || inSides )
            {
            if( r.Name.Equals("Bottom") )
               inBottom = true;
            else if( r.Name.Equals("Left") )
               inLeft = true;
            else if( r.Name.Equals("Right") )
               inRight = true;
            else if( r.Name.Equals("Top") )
               inTop = true;
            }
            else if( r.Name.Equals("Thickness") )
               inThickness = true;
            break;
         case XmlNodeType.Text:
            if( inInset )
{
if( inBottom )
bottom = XmlConvert.ToInt32(r.Value);
else if( inLeft )
left = XmlConvert.ToInt32(r.Value);
else if( inRight )
right = XmlConvert.ToInt32(r.Value);
else if( inTop )
inset = new Inset(left, XmlConvert.ToInt32(r.Value), right, bottom);
}
else if( inSides )
{
if( inBottom )
this.bottom = XmlConvert.ToBoolean(r.Value.ToLower(CultureInfo.InvariantCulture));
else if( inLeft )
this.left = XmlConvert.ToBoolean(r.Value.ToLower(CultureInfo.InvariantCulture));
else if( inRight )
this.right = XmlConvert.ToBoolean(r.Value.ToLower(CultureInfo.InvariantCulture));
else if( inTop )
this.top = XmlConvert.ToBoolean(r.Value.ToLower(CultureInfo.InvariantCulture));
}
else if( inColor )
color = Serializer.DeserializeColorValue(r.Value);
else if( inThickness )
thickness = XmlConvert.ToInt32(r.Value);
break;
case XmlNodeType.EndElement:
if( inInset && r.Name.Equals("Inset") )
inInset = false;
else if( inColor && r.Name.Equals("Color") )
inColor = false;
else if( inSides && r.Name.Equals("Sides") )
inSides = false;
else if( inInset || inSides )
{
if( inBottom && r.Name.Equals("Bottom") )
inBottom = false;
else if( inLeft && r.Name.Equals("Left") )
inLeft = false;
else if( inRight && r.Name.Equals("Right") )
inRight = false;
else if( inTop && r.Name.Equals("Top") )
inTop = false;
}
else if( inThickness && r.Name.Equals("Thickness") )
// return here so that subclasses can deserialize
return true;
break;
}
}
return true;
}
```

**Example**
This is an example of what a derived class might do in overrides for those methods:

```csharp
// A constructor with no arguments is required for ISerializeSupport.
// It does not need to be public though.
protected GradientLineBorder() : LineBorder(SystemColors.WindowFrame, 1, true, true, true, true)
{
startColor = Color.Blue;
endColor = Color.Green;
}
/// <p>DOC-SUMMARY-TAG-OPEN</p>
/// Saves the object to XML.
/// <p>DOC-SUMMARY-TAG-CLOSE</p>
/// <param name="w">XmlTextWriter object to which to save the object</param>
/// <returns>true if successful; false otherwise</returns>
public override bool Serialize(XmlTextWriter w)
{
if( !base.Serialize(w) )
return false;
Serializer.SerializeColor(startColor, "StartColor", w);
Serailizer.SerializeColor(endColor, "EndColor", w);
return true;
}
/// <p>DOC-SUMMARY-TAG-OPEN</p>
/// Loads the object from XML.
/// <p>DOC-SUMMARY-TAG-CLOSE</p>
/// <param name="r">XmlNodeReader from which to load the object</param>
/// <returns>true if successful; false otherwise</returns>
public override bool Deserialize(XmlNodeReader r)
{
bool inStartColor = false;
bool inEndColor = false;

if( r.IsEmptyElement )
return true;
if( !base.Deserialize(r) )
return false;
while( r.Read() )
{
switch( r.NodeType )
{
case XmlNodeType.Element:
if( r.Name.Equals("StartColor") )
inStartColor = true;
else if( r.Name.Equals("EndColor") )
inEndColor = true;
case XmlNodeType.Text:
if( inStartColor )
startColor = Serializer.DeserializeColorValue(r.Value);
else if( inEndColor )
endColor = Serializer.DeserializeColorValue(r.Value);
break;
case XmlNodeType.EndElement:
if( inStartColor && r.Name.Equals("StartColor") )
inStartColor = false;
else if( inEndColor && r.Name.Equals("EndColor") )
// return here so that subclasses can deserialize
return true;
break;
}
}
return true;
}
```

**Example**
Another (somewhat easier) way to implement it would be this way:

```csharp
/// <p>DOC-SUMMARY-TAG-OPEN</p>
/// Saves the object to XML.
/// <p>DOC-SUMMARY-TAG-CLOSE</p>
/// <param name="w">XmlTextWriter object to which to save the object</param>
/// <returns>true if successful; false otherwise</returns>
public override bool Serialize(XmlTextWriter w)
{ w.WriteAttributeString("startColor", Serializer.SerializeColorValue(startColor));
w.WriteAttributeString("endColor", Serializer.SerializeColorValue(endColor));
return base.Serialize(w);
}
/// <p>DOC-SUMMARY-TAG-OPEN</p>
/// Loads the object from XML.
/// <p>DOC-SUMMARY-TAG-CLOSE</p>
/// <param name="r">XmlNodeReader from which to load the object</param>
/// <returns>true if successful; false otherwise</returns>
public override bool Deserialize(XmlNodeReader r)
{

if( r.MoveToAttribute("startColor") )
startColor = Serializer.DeserializeColorValue(r.Value);
if( r.MoveToAttribute("endColor") )
endColor = Serializer.DeserializeColorValue(r.Value);
return base.Deserialize(r); }
```

## See Also

[Parsing Formulas in Custom XML Deserialization](/spreadnet/docs/latest/online-win/overview/spwin-devguide/spwin-usefiles/spwin-serializeops/spwin-open-parseformulas)