I'm using the protobuf-net
nuget package and have a package-specific question
I have a project with a core package and a main package which includes various other small packages. I have simplified this below. I was wondering, how can I serialize it from the Core
package? Ideally, without the reference in the Api
package as others may make their own extensions and I can't add [ProtoInclude(...)]
in the API package for everyone.
Would it be something to do with the Not Like Attributes section? If so, is there somewhere you can point me for implementation?
Api:
public interface IBase
{
string typeRef { get; }
Connector Connector { get; set; } // Custom connector
object Build(); // Bunch of params
}
Core:
[ProtoContract]
public class Main
{
[ProtoMember(1)]
public ICollection<IBase> Items { get; set; }
}
[ProtoContract]
public class Group : IBase
{
public string typeRef => "G";
[ProtoMember(2)]
public Connector Connector { get; set; }
[ProtoMember(3)]
public string? AnExpression { get; set; }
[ProtoMember(4)]
public ICollection<IBase> Items { get; set; }
}
[ProtoContract]
public class Base<T> : IBase
{
public string typeRef => "S";
[ProtoMember(2)]
public Connector Connector { get; set; }
[ProtoMember(3)]
public string? Operation{ get; set; }
[ProtoMember(4)]
public IEnumerables<T?> Values { get; set; }
}
So after some rummaging for code and some testing, I finally got a working solution.
To limit the startup code that needed to be added to people's projects, I found a solution that only added the references once serialization was actually requested see comment 1.
The method fills the stream argument. However, I also added a byte[] method that creates a MemoryStream
, passes that in and passes the .ToArray()
out.
I ran into some complications with the fact that Base<T>
has a generic type. This meant that I had to track the types that had already been added (as adding them again would throw an error). Doing this meant that if a new base was added it would still get serialised
public void SerializeTo(System.IO.Stream stream)
{
// 1) I HAVE DONE THIS IN A METHOD (with a type tracker list)
// BUT YOU CAN DO IT AT RUNTIME
if (TypeTracker.ProtoTypes == null)
{
RuntimeTypeModel.Default.Add(typeof(Connector), true);
// There was actually another interface,
// so I had to add these references
// => Group implements IGroup which implements IBase
RuntimeTypeModel.Default.Add(typeof(IGroup), false)
.AddSubType(45, typeof(Group));
RuntimeTypeModel.Default.Add(typeof(IBase), false)
.AddSubType(45, typeof(IGroup));
// DECLARE THE LIST
TypeTracker.ProtoTypes = new List<Type>();
}
var pTypes = TypeTracker.ProtoTypes;
// This is a IGroup
_myGroup.PrepForSerialisation(
RuntimeTypeModel.Default.Add(typeof(IBase), false),
ref pTypes
);
Serializer.Serialize(stream, _group as FilterGroup);
}
// Group implements it as
public void PrepForSerialisation(MetaType ifilterBase, ref ICollection<Type> fTypes)
{
foreach (var item in Items)
if (item is ISerializer ss)
ss.PrepForSerialisation(ifilterBase, ref fTypes);
}
// Base<T> implements it as
public void PrepForSerialisation(MetaType ifilterBase, ref ICollection<Type> fTypes)
{
var thisType = this.GetType();
// Make sure I've not already been defined
if (!fTypes.Contains(thisType))
{
// Use the fType count, so there are never any clashes
ifilterBase.AddSubType(50 + fTypes.Count(), thisType);
// Add this type to the list
fTypes.Add(thisType);
}
}
In case you want to see the `TypeTracker` class
internal sealed class TypeTracker
{
TypeTracker () { }
static ICollection<Type>? _protoTypes;
public static ICollection<Type>? ProtoTypes
{
get => _protoTypes;
set
{
_protoTypes = value;
}
}
}