Eric
Eric

Reputation: 3087

Differences with BinaryFormatter

I am trying to change the serializer in an existing WCF net.tcp project that uses shared entities on client & server. I am having a hard time figuring out protobuf-net(V2480) The chart here says I can serialize private members but cannot find documentation to do that, is it possible without attributes? How do I enable graph mode(As Reference) as explained here

Will that solve the issue of protobuf triggering my changed items flag? For example I have a class

 public enum FirstEnum
{ 
    First = 0,
    Second,
    Third
}
public enum AnotherEnum
{ 
    AE1 = 0,
    AE2,
    AE3
}
[Serializable()]
public class SomeClass
{
    public int SomeClassId { get; set; }
    public FirstEnum FEnum { get; set; }
    public AnotherEnum AEnum { get; set; }
    string thing;
    public string Thing
    {
        get{return thing;}
        set
        {
            if (string.IsNullOrEmpty(value))
                throw new ArgumentNullException("Thing");

            thing = value;
        }
    }

    private decimal firstAmount;
    public decimal FirstAmount 
    { 
        get{return firstAmount;} 
        set
        {
            if (value != firstAmount)
            {
                firstAmount = value;
                changedItems.Add("FirstAmount changed");
            }
        }
    }
    private decimal secondAmount;
    public decimal SecondAmount
    {
        get { return secondAmount; }
        set
        {
            if (value != secondAmount)
            {
                secondAmount = value;
                changedItems.Add("SecondAmount changed");
            }
        }
    }
    public decimal ThirdAmount { get { return SecondAmount - FirstAmount; } }
    public DateTime? SomeDate { get; set; }

    private List<string> changedItems = new List<string>();
    public List<string> ChangedItems
    {
        get { return changedItems; }
    }
public int PrivateSet { get; private set; }
    public SomeClass() { }

    public SomeClass(decimal first, decimal second)
    {
        FirstAmount = first;
        SecondAmount = second;
    }

    public void ClearChangedItems()
    {
        changedItems.Clear();
    }

When I deserialize it with (1000 items)

 var model = CreateModel();
 items = (List<SomeClass>)model.Deserialize(returnStream, null, typeof(List<SomeClass>));

2012-04-06 09:14:28.1222|DEBUG|ProtobufTEsts.Form1|ProtoBuf Number of changed items : 1000

With BinaryForrmatter

System.Runtime.Serialization.Formatters.Binary.BinaryFormatter binaryFormatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
items = (List<SomeClass>)binaryFormatter.Deserialize(returnStream);

2012-04-06 09:14:28.1662|DEBUG|ProtobufTEsts.Form1|BinaryFormatter Number of changed items : 0

Is there a way to get protobuf to behave like the binaryFormatter but preserve the performance of protobuf?

How to allow for private serialization, this fails

 public static TypeModel CreateModel()
    {
        RuntimeTypeModel model = TypeModel.Create();
        ///var metaType = RuntimeTypeModel.Default.Add(typeof(SomeClass), false);
        model.Add(typeof(SomeClass), false)
            .Add(1, "SomeClassId")
            .Add(2, "FEnum")
            .Add(3, "AEnum")
            .Add(4, "Thing")
            .Add(5, "FirstAmount")
            .Add(6, "SecondAmount")
            .Add(7, "SomeDate")
            .Add(8, "PrivateSet");
        TypeModel compiled = model.Compile();
        return compiled;
    }

Upvotes: 3

Views: 770

Answers (2)

Marc Gravell
Marc Gravell

Reputation: 1064114

Ah, I understand the issue now; this line is problematic:

TypeModel compiled = model.Compile();
return compiled;

If you use Compile(), it creates a formal assembly (in memory) that has to obey the usual rules of assemblies, and in particular: member accessibility. This means it can't access your private sertter.

Instead, use:

model.CompileInPlace();
return model;

This performs a partial compilation, but continues using DynamicMethod. This cheeky little critter has options to spoof its way past accessibility rules (much like reflection can), so it can continue to use the private setter.

Note that the model is also compiled-in-place (at a more granular level) on as as-needed basis, so this call to CompileInPlace is not strictly necessary, but helps do everything up-front an in advance.

For completeness, there is an additional Compile(string,string) overload that can be used to produce a separate serialization dll on disk, that can be referenced and used without any meta-programming at runtime.

Upvotes: 2

Marc Gravell
Marc Gravell

Reputation: 1064114

Yes protobuf-net can serialize private fields, and do so without attributes. I'm not at a PC, so this may need tweaking:

var metaType = RuntimeTypeModel.Default.Add(typeof(SomeClass), false);
// for each field in a known order
metaType.Add(fieldName, someUniqueTag);

In attribute-driven usage, there is also ImplicitFields.AllFields which would automatically configure it for the usage you intend, but I haven't yet added an ImplicitFields helper method to MetaType. I will add that to my list!

Note: tag (=field) numbers are important to protobuf and it must be possible to reproduce the same number mappings when you deserialize.

Another option you might want to consider is (de)serialization callbacks, which allow you to know that it is currently serializing/deserializing (via before/after method invokes). This can be another way of disabling side-effects for an interval such as deserialization.

Upvotes: 0

Related Questions