开发者

protobuf-net WCF Multiple Nested Generic Abstract Objects Serialization v282

this is my first post on here, so please bear with me...

I'm trying to nest multiple Generic objects, and then pass them through WCF using ProtoBuf-net. There are many (10+) main objects that I have implemented, tho my code listed will only show 2. They all have similar structures, but there are a few that only use one or two of the generics (hence their inheritance structure)

After working through all the tags and ProtoIncludes, I've been able to get a single main object to serialize. When I started working on the next object, I was getting the error:

Known-type mainBase`2 for ProtoIncludeAttribute must be a direct subclass of mainBase`1

After Racking my brain for a few hours (and reading up on here) I got desperate and was starting to try some random stuff. When I removed the ProtoInclude for the original main object and only had them for the second one, it worked fine!

In the code below I have all the tags still implemented so that you can get the exception, however if you comment out either aMain or bMain in all 4 of the mainBase classes, the program will be able to serialize whichever one is currently tagged.

(I apologize in advance, the code is a big large, but I haven't found a problem as complex as mine yet)

class Program
{
    static void Main(string[] args)
    {
        var vcc = new aMain();
        var vccStream = new MemoryStream();
        ProtoBuf.Serializer.Serialize(vccStream, vcc);
        vccStream.Position = 0;
        var newvcc = ProtoBuf.Serializer.Deserialize<aMain>(vccStream);


        var vtc = new bMain();
        var vtcStream = new MemoryStream();
        ProtoBuf.Serializer.Serialize(vtcStream, vtc);
        vtcStream.Position = 0;
        var newvtc = ProtoBuf.Serializer.Deserialize<bMain>(vtcStream);
    }
}

#region Problem Objects, 'Main Objects' Base

[DataContract, ProtoContract, Serializable]
[ProtoInclude(2, typeof(aMain))]
[ProtoInclude(3, typeof(bMain))]
public abstract class mainBase<TbbBase, TaBase, TcbBase>
    : mainBase<TbbBase, TaBase>
    where TcbBase : cbBase
    where TbbBase : bbBase
    where TaBase : aBase
{
    [DataMember, ProtoMember(1)]
    public TcbBase Value3 { get; set; }

    protected mainBase()
    {
        Value3 = Activator.CreateInstance(typeof(TcbBase)) as TcbBase;
    }
}

[DataContract, ProtoContract, Serializable]
[ProtoInclude(2, typeof(mainBase<aMainSub_bbBase, aMainSub_aBase, aMainSub_cbBase>))]
[ProtoInclude(3, typeof(mainBase<bMainSub_bbBase, bMainSub_aBase, bMainSub_cbBase>))]
public abstract class mainBase<TbbBase, TaBase>
    : m开发者_如何转开发ainBase<TbbBase>
    where TbbBase : bbBase
    where TaBase : aBase
{
    [DataMember, ProtoMember(1)]
    public TaBase Value2 { get; set; }

    protected mainBase()
    {
        Value2 = Activator.CreateInstance(typeof(TaBase)) as TaBase;
    }
}

[DataContract, ProtoContract, Serializable]
[ProtoInclude(2, typeof(mainBase<aMainSub_bbBase, aMainSub_aBase>))]
[ProtoInclude(3, typeof(mainBase<bMainSub_bbBase, bMainSub_aBase>))]
public abstract class mainBase<TbbBase> : mainBase
    where TbbBase : bbBase
{
    [DataMember, ProtoMember(1)]
    public TbbBase Value1 { get; set; }

    protected mainBase()
    {
        Value1 = Activator.CreateInstance(typeof(TbbBase)) as TbbBase;
    }
}

[DataContract, ProtoContract, Serializable]
[ProtoInclude(1, typeof(mainBase<aMainSub_bbBase>))]
[ProtoInclude(2, typeof(mainBase<bMainSub_bbBase>))]
public abstract class mainBase
{
    public abstract string MyDefaultNameSpace { get; }
}

#endregion

#region Main Objects

[DataContract, ProtoContract, Serializable]
public class aMain : mainBase<aMainSub_bbBase, aMainSub_aBase, aMainSub_cbBase>
{
    public override string MyDefaultNameSpace { get { return "VideoChunker"; } }
}

[DataContract, ProtoContract, Serializable]
public class aMainSub_bbBase : bbbbBase { }

[DataContract, ProtoContract, Serializable]
public class aMainSub_aBase : aaBase { }

[DataContract, ProtoContract, Serializable]
public class aMainSub_cbBase : cbBase { }


[DataContract, ProtoContract, Serializable]
public class bMain : mainBase<bMainSub_bbBase, bMainSub_aBase, bMainSub_cbBase>
{
    public override string MyDefaultNameSpace { get { return "VideoTranscoder"; } }
}

[DataContract, ProtoContract, Serializable]
public class bMainSub_bbBase : bbbbBase { }

[DataContract, ProtoContract, Serializable]
public class bMainSub_aBase : aaBase { }

[DataContract, ProtoContract, Serializable]
public class bMainSub_cbBase : cbBase { }

#endregion

#region Base Objects

[DataContract, ProtoContract, Serializable]
[ProtoInclude(2, typeof(aMainSub_bbBase))]
[ProtoInclude(3, typeof(bMainSub_bbBase))]
public abstract class bbbbBase : bbbBase { }

[DataContract, ProtoContract, Serializable]
[ProtoInclude(1, typeof(bbbbBase))]
public abstract class bbbBase : bbBase { }

[DataContract, ProtoContract, Serializable]
[ProtoInclude(1, typeof(bbbBase))]
public abstract class bbBase : bBase { public override string GetConfigNamespace { get { return ".Service"; } } }

[DataContract, ProtoContract, Serializable]
[ProtoInclude(1, typeof(bbBase))]
[ProtoInclude(2, typeof(cbBase))]
public abstract class bBase : subBase { }

[DataContract, ProtoContract, Serializable]
[ProtoInclude(1, typeof(aMainSub_cbBase))]
[ProtoInclude(2, typeof(bMainSub_cbBase))]
public class cbBase : bBase { public override string GetConfigNamespace { get { return ".Fabric"; } } }

[DataContract, ProtoContract, Serializable]
[ProtoInclude(1, typeof(bBase))]
[ProtoInclude(4, typeof(aBase))]
public abstract class subBase { public virtual string GetConfigNamespace { get { return string.Empty; } } }

[DataContract, ProtoContract, Serializable]
[ProtoInclude(2, typeof(aMainSub_aBase))]
[ProtoInclude(3, typeof(bMainSub_aBase))]
public abstract class aaBase : aBase { }

[DataContract, ProtoContract, Serializable]
[ProtoInclude(1, typeof(aaBase))]
public abstract class aBase : subBase { public override string GetConfigNamespace { get { return ".Action"; } } }

#endregion

Since I am using the older version of protobuf, I decided to pull down its source and see if I could figure anything out. After a bit of debugging I found where the exception was being thrown, and I simply do a continue; instead of throwing the exception.

In the file SerializerT.cs, line 246 is the following:

foreach (ProtoIncludeAttribute pia in Attribute.GetCustomAttributes(typeof(T), typeof(ProtoIncludeAttribute), false))
            {
                Type subclassType = pia.ResolveKnownType(typeof(T).Assembly);
                if (subclassType == null)
                {
                    throw new ProtoException("Unable to identify known-type for ProtoIncludeAttribute: " + pia.KnownTypeName);
                }
                if (subclassType.BaseType != typeof(T))
                {
                    continue;
                    throw new ProtoException(string.Format(
                        "Known-type {0} for ProtoIncludeAttribute must be a direct subclass of {1}",
                        subclassType.Name, typeof(T).Name));
                }
                Property<T, T> prop;
                switch (pia.DataFormat)
                {
                    case DataFormat.Default:
                        prop = (Property<T, T>) PropertyUtil<T>.CreateTypedProperty("CreatePropertyMessageString", typeof(T), typeof(T), subclassType);
                        break;
                    case DataFormat.Group:
                        prop = (Property<T, T>)PropertyUtil<T>.CreateTypedProperty("CreatePropertyMessageGroup", typeof(T), typeof(T), subclassType);
                        break;
                    default:
                        throw new ProtoException("Invalid ProtoIncludeAttribute data-format: " + pia.DataFormat);
                }
                // check for duplicates
                if (tagsInUse.Contains(pia.Tag))
                {
                    throw new InvalidOperationException(
                        string.Format("Duplicate tag {0} detected in sub-type {1}", pia.Tag, subclassType.Name));
                }
                tagsInUse.Add(pia.Tag);
                prop.Init(pia.Tag, pia.DataFormat, PropertyFactory.GetPassThru<T>(), null, true, null);
                subclassList.Add(new KeyValuePair<Type, Property<T, T>>(subclassType, prop));
            }

You can see where my continue' is right above the original throw. I'm not exactly sure what the ramifications are of this action; is this an actual bug or am I opening myself up to some catastrophic craziness?

Thanks for your time.


OK, I think I understand the model now (very glad I didn't look too hard last night ;p) - you have:

aMain
 : mainBase<aMainSub_bbBase, aMainSub_aBase, aMainSub_cbBase>
  : mainBase<aMainSub_bbBase, aMainSub_aBase>
   : mainBase<aMainSub_bbBase> : mainBase

bMain
 : mainBase<bMainSub_bbBase, bMainSub_aBase, bMainSub_cbBase>
  : mainBase<bMainSub_bbBase, bMainSub_aBase>
   : mainBase<bMainSub_bbBase> : mainBase

aMainSub_bbBase, bMainSub_bbBase
 : bbbbBase : bbbBase : bbBase : bBase : subBase
aMainSub_aBase, bMainSub_aBase
 : aaBase : aBase : subBase
aMainSub_cbBase, bMainSub_cbBase
 : cbBase : bBase : subBase

This is actually a similar issue to this question, and relates to attributes generally, in that the attributes apply to all closed types, not just the one you were thinking of. In particular, you are currently telling it that aMain relates to both mainBase<aMainSub_bbBase, aMainSub_aBase, aMainSub_cbBase> and mainBase<bMainSub_bbBase, bMainSub_aBase, bMainSub_cbBase> (and the same for all the crossovers there).

With two similar questions in a short span, I will try and look at this, but short-term I think the v2 modeller is the way to fix this; I have removed the ProtoInclude attributes from the 3 generic types (mainBase<>, mainBase<,> and mainBase<,,>), then:

var model = RuntimeTypeModel.Default;

model[typeof(mainBase<aMainSub_bbBase>)].AddSubType(2, typeof(mainBase<aMainSub_bbBase, aMainSub_aBase>));
model[typeof(mainBase<bMainSub_bbBase>)].AddSubType(2, typeof(mainBase<bMainSub_bbBase, bMainSub_aBase>));

model[typeof(mainBase<aMainSub_bbBase, aMainSub_aBase>)].AddSubType(2, typeof(mainBase<aMainSub_bbBase, aMainSub_aBase, aMainSub_cbBase>));
model[typeof(mainBase<bMainSub_bbBase, bMainSub_aBase>)].AddSubType(2, typeof(mainBase<bMainSub_bbBase, bMainSub_aBase, bMainSub_cbBase>));

model[typeof(mainBase<aMainSub_bbBase, aMainSub_aBase, aMainSub_cbBase>)].AddSubType(2, typeof(aMain));
model[typeof(mainBase<bMainSub_bbBase, bMainSub_aBase, bMainSub_cbBase>)].AddSubType(2, typeof(bMain));

(the regular attributes handle the majority of the cases)

Note as a slight "feature" of this that since we are in parallel branches, you don't need separate 2 / 3 tags, as it isn't that you expect either an aMain or a bMain from a mainBase<aMainSub_bbBase, aMainSub_aBase, aMainSub_cbBase> - only aMain is possible.

I will have to investigate options for making this cleaner - but at least in v2 it can work!

Re removing the existing exception; I honestly don't believe that the v1 code has the subtlety to correctly handle this scenario. Removing that exception could cause it to fail in other interesting ways, in particular when it tries to work down the type hierarchy. By all means make any changes you choose to your local copy, but: at own risk - I can't say "yes that is safe", as I don't believe it is. My recommendation here is to use the more sophisticated modelling in v2, while I investigate ways of handling this type of parallel generic model. Any changes here will only apply to v2, as v1 simply has no sane way of storing this information, without essentially introducing the v2 type-modeller, i.e. making v1 into v2.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜