Thursday, May 10, 2007

.NET Interface Serialization Issue

Interface can not be serialized or de-serialized. That's because interface is just a contract not detailed implementation. However, in some cases, we do want to expose interface so objects are not restricted by a specific type.

Look at a simple example:
    public interface IPriceable
{
double GetPrice();
}
[Serializable]
public class ProductA : IPriceable
{
public double GetPrice()
{
return 123.345;
}
}

[Serializable]
public class ProductB : IPriceable
{
public double GetPrice()
{
return 234.567;
}
}

[Serializable]
public class PromotionPackage
{
public List<IPriceable> Items = new List<IPriceable>();
double GetPromotePrice()
{
//return PromotionCaculator.GetPrice(_items);
return 567.89; ;
}
}
The PromotionPackage contains a list of IPriceable objects (ProductA, ProductB etc.). Everything is okay except that an exception will be thrown when we serialize the PromotionPackage object:
{"Cannot serialize member PromotionPackage.Items of type IPriceable because it is an interface."}
A workaround would be wrapping the interface to an abstract class:
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;

public class Program
{
public interface IPriceable
{
double GetPrice();
}

[XmlInclude(typeof(ProductA))]
[XmlInclude(typeof(ProductB))]
public abstract class ProductBase : IPriceable
{
public abstract double GetPrice();
}

[Serializable]
public class ProductA : ProductBase
{
public override double GetPrice()
{
return 123.345;
}
}

[Serializable]
public class ProductB : ProductBase
{
public override double GetPrice()
{
return 234.567;
}
}

[Serializable]
public class PromotionPackage
{
public List<ProductBase> Items = new List<ProductBase>();
double GetPromotePrice()
{
//return PromotionCaculator.GetPrice(_items);
return 567.89; ;
}
}

static void Main(string[] args)
{
PromotionPackage package = new PromotionPackage();
package.Items.Add(new ProductA());
package.Items.Add(new ProductB());

string xmlString = string.Empty;
XmlSerializer serializer = new XmlSerializer(typeof(PromotionPackage));

// Serialize
using (StringWriter sw = new StringWriter())
{
serializer.Serialize(sw, package);
xmlString = sw.ToString();
}

// De-serialize
PromotionPackage newPackage = null;
using (StringReader sr = new StringReader(xmlString))
{
XmlTextReader reader = new XmlTextReader(sr);
newPackage = (PromotionPackage)serializer.Deserialize(reader);
}

Console.WriteLine("Done.");
Console.Read();
}
}

Saturday, May 05, 2007

A Managed Way To Serialize & Deserialze DateTime field

public class MyEvent
{
public string Name;

[XmlIgnore]
public DateTime Time;
[XmlElement("Time")]
public string TimeString
{
get { return Time.ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture); }
set { Time = DateTime.ParseExact(value, "yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture); }
}

public MyEvent() { }
public MyEvent(string name, DateTime time)
{
Name = name;
Time = time;
}

static void Main(string[] args)
{
MyEvent evt = new MyEvent("event1", new DateTime(2007, 5, 5));

XmlSerializer serializer = new XmlSerializer(typeof(MyEvent));
TextWriter writer = new StreamWriter(@"c:\event.xml");
serializer.Serialize(writer, evt);
writer.Close();
}
}
Here TimeString is exposed as a public property. Can we make it private? The answer is yes, but it requires the DataContract new in .NET 3.0:
[DataContract]
public class MyEvent
{
[DataMember]
public string Name;

public DateTime Time;

[DataMember(Name="Time") ]
private string TimeString
{
get { return Time.ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture); }
set { Time = DateTime.ParseExact(value, "yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture); }
}

public MyEvent(string name, DateTime time)
{
Name = name;
Time = time;
}

static void Main(string[] args)
{
MyEvent evt = new MyEvent("event1", new DateTime(2007, 5, 5));

DataContractSerializer serializer = new DataContractSerializer(typeof(MyEvent));
XmlWriter writer = XmlWriter.Create(@"c:\event.xml");
serializer.WriteObject(writer, evt);
writer.Close();
}
}