Search code examples
c#c#-4.0serializationtuplesdatacontractserializer

How can I serialize a tuple containing a basic type and an array of a basic type?


In C# 4.0, I'm trying to serialize and deserialize a Tuple<Guid, int[]> using DataContractSerializer. I've successfully serialized and deserialized type Guid, type int[] and type Tuple<Guid, int>. If I try to serialize type Tuple<Guid, int[]>, everything compiles, but I get the following runtime exception:

Type 'System.Int32[]' with data contract name 
'ArrayOfint:http://schemas.microsoft.com/2003/10/Serialization/Arrays'
is not expected. Consider using a DataContractResolver or add any types
not known statically to the list of known types - for example, by using
the KnownTypeAttribute attribute or by adding them to the list of known 
types passed to DataContractSerializer.

My serialization and deserialization routines are simple:

public static string Serialize<T>(this T obj)
{
    var serializer = new DataContractSerializer(obj.GetType());
    using (var writer = new StringWriter())
    using (var stm = new XmlTextWriter(writer))
    {
        serializer.WriteObject(stm, obj);
        return writer.ToString();
    }
}

public static T Deserialize<T>(this string serialized)
{
    var serializer = new DataContractSerializer(typeof(T));
    using (var reader = new StringReader(serialized))
    using (var stm = new XmlTextReader(reader))
    {
        return (T)serializer.ReadObject(stm);
    }
}

Why am I getting this exception, and what can I do to resolve this or get around it? It would seem to me that a Tuple containing types that can be serialized should have no trouble being serialized.


Solution

  • How about something like this?

    class Program
    {
        public static class DataContractSerializerFactory<T>
        {
            private static IEnumerable<Type> GetTypeArguments(Type t, IEnumerable<Type> values)
            {
                if (t.IsGenericType)
                    foreach (var arg in t.GetGenericArguments())
                        values = values.Union(GetTypeArguments(arg, values));
                else
                    values = values.Union(new[] { t });
                return values;
            }
    
            public static DataContractSerializer Create()
            {
                return new DataContractSerializer(typeof(T), GetTypeArguments(typeof(T), new[] { typeof(T) }));
            }
        }
    
        static void Main(string[] args)
        {
            var x = Tuple.Create(Guid.NewGuid(), new[] { 1, 2, 3, 4, 5, 6 });
    
            var serializer = DataContractSerializerFactory<Tuple<Guid, int[]>>.Create();
    
            var sb = new StringBuilder();
            using (var writer = XmlWriter.Create(sb))
            {
                serializer.WriteObject(writer, x);
                writer.Flush();
                Console.WriteLine(sb.ToString());
            }
        }
    }
    

    EDIT: Ought to work for any nested generic. Only the base type and leaf type arguments are considered. Should be easy if you want the in-between containers to be part of KnownTypes too.