Search code examples
c#protobuf-net

Serialise `Interface` from package reference


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; }
}

Solution

  • 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;
            }
        }
    }