2
votes

I am trying to serialize into protobuf format a C# class that has properties on it that are domain specific types. But these types should be handled as value types.

I am using protobuf.net.

An example is this

[ProtoContract]
public class TestClass
{
    [ProtoMember(1)]
    public string StringProperty { get; set; }

    [ProtoMember(2)]
    public DomainTypeToBeHandldedAsString DomainTypeToBeHandldedAsString { get; set; }
}

public class DomainTypeToBeHandldedAsString
{
    public string Value { get; set; }

    public static implicit operator string(DomainTypeToBeHandldedAsString domainType)
    {
        return domainType?.Value;
    }

    public static implicit operator DomainTypeToBeHandldedAsString(string s)
    {
        return new DomainTypeToBeHandldedAsString {Value = s};
    }
}

In the serialized message I do not want people to care about DomainTypeToBeHandldedAsString. I just want them to see a string and send a string.

So I tried doing this:

var model = ProtoBuf.Meta.RuntimeTypeModel.Default;
model.Add(typeof(DomainTypeToBeHandldedAsString), false).SetSurrogate(typeof(string));

But this fails with this exception:

System.ArgumentException: 'Repeated data (a list, collection, etc) has inbuilt behaviour and cannot be used as a surrogate'

Is there a way to specify a custom serializer for such a class? And it should be said that there are also domain types that should be treated as int or other value types. So not just string.

Thanks

EDIT

I tried this in addition:

[ProtoContract]
public class TestClass
{
    [ProtoMember(1)]
    public string StringProperty { get; set; }

    public DomainTypeToBeHandldedAsInt DomainTypeToBeHandldedAsInt { get; set; }
}

[ProtoContract]
public class DomainTypeToBeHandldedAsInt : IConvertible
{
    [ProtoMember(1)]
    public int Value { get; set; }

    public static implicit operator int(DomainTypeToBeHandldedAsInt domainType)
    {
        return domainType?.Value ?? 0;
    }

    public static implicit operator DomainTypeToBeHandldedAsInt(int s)
    {
        return new DomainTypeToBeHandldedAsInt { Value = s };
    }

    public int ToInt32(IFormatProvider provider)
    {
        return Value;
    }

    //All the rest throw a NotImplementedException
}

I then added the type to the RunTimeModel like this:

var model = ProtoBuf.Meta.RuntimeTypeModel.Default;
model[typeof(TestClass)].Add(2, "DomainTypeToBeHandldedAsInt", typeof(Int32), null);

And that resulted in a .proto file like this:

syntax = "proto3";
package ProtoBufferSerializerTest;

message TestClass {
   string StringProperty = 1;
   repeated int32 DomainTypeToBeHandldedAsInt = 2 [packed = false];
}

But I do not want this to be repeated and I don't need the [packed = false] either.

1

1 Answers

2
votes

That's a very interesting question. At the moment, the answer would be "no, that isn't supported", but: the following is supported, if you enable AllowParseableTypes (on the RuntimeTypeModel instance - RuntimeTypeModel.Default is the instance behind Serializer.* methods):

public class DomainTypeToBeHandldedAsString
{
    public string Value { get; set; }
    public override string ToString() => Value;
    public static DomainTypeToBeHandldedAsString Parse(string s)
        => new DomainTypeToBeHandldedAsString { Value = s };
}

Basically, it looks for the public static {Type} Parse(string) pattern. But: I agree that it would be better to express this explicitly, and the "surrogate as string" is a nice way to convey this. I would be open to adding direct support for what you have in the question, but that isn't there today and would need some code changes. Not very many, probably, since the essentials of the feature already exist via parseable-types!

Here's the parseable-types approach working in an example that should work today:

using ProtoBuf;
using ProtoBuf.Meta;

public class DomainTypeToBeHandldedAsString
{
    public string Value { get; set; }
    public override string ToString() => Value;
    public static DomainTypeToBeHandldedAsString Parse(string s)
        => new DomainTypeToBeHandldedAsString { Value = s };
}
[ProtoContract]
public class Bar
{
    [ProtoMember(1)]
    public DomainTypeToBeHandldedAsString A { get; set; }
}
class Program
{
    static void Main()
    {
        RuntimeTypeModel.Default.AllowParseableTypes = true;
        var obj = new Bar { A = new DomainTypeToBeHandldedAsString { Value = "abcdef" } };
        var clone = Serializer.DeepClone(obj);
        System.Console.WriteLine(clone.A.Value);
    }
}