I have a class hierarchy that looks like this. These classes contain a lot of other details which I have excluded. This is a simplification to focus on the serialization a
OK; with the updated question I understand a bit more. I expect the generics in the middle of the object-model are indeed going to make life... "fun". It doesn't work "out of the box"; I looked to see if there were some simple tweaks I could make to support it, but it started getting ugly pretty quickly. I expect it would be better to simply remove the need for the generic in the middle, if possible - perhaps retaining a generic interface (rather than the generic class). Here's some code that does work; how this maps to your code... I can't tell 100%. Note you don't have to use the TypeDescriptor
stuff (etc) - it just seemed that since you are using code-gen this might make some things easier...
(I didn't check the DataSet
stuff - just the class stuff)
using System;
using System.ComponentModel;
using System.Data;
using System.IO;
using NUnit.Framework;
using ProtoBuf;
[TestFixture]
public class ComplexGenericTest
{
[Test]
public void TestX()
{
Query query = new X { Result = "abc" };
Assert.AreEqual(typeof(string), query.GetQueryType());
Query clone = Serializer.DeepClone<Query>(query);
Assert.IsNotNull(clone);
Assert.AreNotSame(clone, query);
Assert.IsInstanceOfType(query.GetType(), clone);
Assert.AreEqual(((X)query).Result, ((X)clone).Result);
}
[Test]
public void TestY()
{
Query query = new Y { Result = 1234};
Assert.AreEqual(typeof(int), query.GetQueryType());
Query clone = Serializer.DeepClone<Query>(query);
Assert.IsNotNull(clone);
Assert.AreNotSame(clone, query);
Assert.IsInstanceOfType(query.GetType(), clone);
Assert.AreEqual(((Y)query).Result, ((Y)clone).Result);
}
}
public static class QueryExt {
public static Type GetQueryType(this IQuery query)
{
if (query == null) throw new ArgumentNullException("query");
foreach (Type type in query.GetType().GetInterfaces())
{
if (type.IsGenericType
&& type.GetGenericTypeDefinition() == typeof(IQuery<>))
{
return type.GetGenericArguments()[0];
}
}
throw new ArgumentException("No typed query implemented", "query");
}
}
public interface IQuery
{
string Result { get; set; }
}
public interface IQuery<T> : IQuery
{
new T Result { get; set; }
}
[ProtoInclude(21, typeof(W))]
[ProtoInclude(22, typeof(X))]
[ProtoInclude(23, typeof(Y))]
[ProtoInclude(25, typeof(SpecialQuery))]
[ProtoContract]
abstract class Query : IQuery
{
public string Result
{
get { return ResultString; }
set { ResultString = value; }
}
protected abstract string ResultString { get; set; }
// these are to allow simple ResultString implementations
// without the codegen having to worry about int.Parse etc
protected static string FormatQueryString<T>(T value)
{
return TypeDescriptor.GetConverter(typeof(T))
.ConvertToInvariantString(value);
}
protected static T ParseQueryString<T>(string value)
{
return (T) TypeDescriptor.GetConverter(typeof(T))
.ConvertFromInvariantString(value);
}
}
[ProtoContract]
[ProtoInclude(21, typeof(Z))]
abstract class SpecialQuery : Query, IQuery<DataSet>
{
public new DataSet Result { get; set; }
[ProtoMember(1)]
protected override string ResultString
{
get {
if (Result == null) return null;
using (StringWriter sw = new StringWriter())
{
Result.WriteXml(sw, XmlWriteMode.WriteSchema);
return sw.ToString();
}
}
set {
if (value == null) { Result = null; return; }
using (StringReader sr = new StringReader(value))
{
DataSet ds = new DataSet();
ds.ReadXml(sr, XmlReadMode.ReadSchema);
}
}
}
}
[ProtoContract]
class W : Query, IQuery<bool>
{
[ProtoMember(1)]
public new bool Result { get; set; }
protected override string ResultString
{
get {return FormatQueryString(Result); }
set { Result = ParseQueryString<bool>(value); }
}
}
[ProtoContract]
class X : Query, IQuery<string>
{
[ProtoMember(1)]
public new string Result { get; set; }
protected override string ResultString
{
get { return Result ; }
set { Result = value; }
}
}
[ProtoContract]
class Y : Query, IQuery<int>
{
[ProtoMember(1)]
public new int Result { get; set; }
protected override string ResultString
{
get { return FormatQueryString(Result); }
set { Result = ParseQueryString<int>(value); }
}
}
[ProtoContract]
class Z : SpecialQuery
{
}
I'm not 100% sure I understand the scenario you want to model; however, [ProtoInclude]
only looks one level of inheritance away.
If I understand correctly, try the following; note that you would need to know the potential generic types at compile time at the moment:
using System;
using ProtoBuf;
[ProtoContract]
[ProtoInclude(2, typeof(Response))]
[ProtoInclude(3, typeof(Query))]
class Packet
{
[ProtoMember(1)]
int ID;
}
[ProtoContract]
[ProtoInclude(1, typeof(Response<int>))]
[ProtoInclude(2, typeof(Response<decimal>))]
[ProtoInclude(3, typeof(Response<string>))]
class Response : Packet
{
}
[ProtoContract]
class Response<T> : Response
{
[ProtoMember(2)]
public T Value;
public override string ToString()
{
return typeof(T).Name + ": " + Value;
}
}
static class Program
{
static void Main()
{
Packet packet = new Response<int> { Value = 123 };
Packet clone = Serializer.DeepClone<Packet>(packet);
Console.WriteLine(clone.ToString()); // should be int/123
}
}