Search code examples
c#.net-coreprotocol-buffersprotobuf-net

protobuf-net deserializing Type created in .NET Core incorrectly in .NET Framework


If I serialize my class in an app running .NET Core, and then load it in an app running on the full .NET Framework, any variables of type Type are deserialized as nulls.

Here's my demo code:

public class protobufTest
{
    string framework;

    public protobufTest()
    {
        string writeToFile1 = @"C:\temp\testClassNetFramework.pbf", writeToFile2 = @"C:\temp\testClassNET.pbf";
        framework = Assembly.GetEntryAssembly()?.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName;
        Console.WriteLine("Current framework is " + framework);
        string writeToFile;
        if (framework.Contains(".NETFramework", StringComparison.OrdinalIgnoreCase)) writeToFile = writeToFile1;
       else if (framework.Contains(".NETCoreApp", StringComparison.OrdinalIgnoreCase)) writeToFile = writeToFile2;
        else throw new Exception("haven't allowed for framework " + framework);
        var tc = make();

        write(tc, writeToFile);
        Console.WriteLine($"\n\nwritten to: {writeToFile}\n{tc.ToString()}");

        if (File.Exists(writeToFile1))
        {
            var tc1 = Load(writeToFile1);
            Console.WriteLine($"\n\nread from: {writeToFile1}\n{tc1.ToString()}");
        }

        if (File.Exists(writeToFile2))
        {
            var tc2 = Load(writeToFile2);
            Console.WriteLine($"\n\nread from: {writeToFile2}\n{tc2.ToString()}");
        }
    }

    testClass make()
    {
        var tc = new testClass();
        tc.frameworkWhenSaved = framework;
        tc.aType = typeof(long);
        tc.types.Add(typeof(int));
        tc.types.Add(typeof(char));
        return tc;
    }

    testClass Load(string fullFileName)
    {
        MemoryStream ms;

        using (FileStream file = new FileStream(fullFileName, FileMode.Open, FileAccess.Read))
        {
            byte[] bytes = new byte[file.Length];
            file.Read(bytes, 0, (int)file.Length);
            ms = new MemoryStream(bytes);
        }

        ms.Position = 0;
        testClass tc = Serializer.Deserialize<testClass>(ms);

        return tc;
    }

    void write(testClass tc, string fullFileName)
    {
        MemoryStream ms = new MemoryStream();
        Serializer.Serialize<testClass>(ms, tc);
        ms.Position = 0;

        using (FileStream file = new FileStream(fullFileName, FileMode.Create, System.IO.FileAccess.Write))
        {
            byte[] bytes = new byte[ms.Length];
            ms.Read(bytes, 0, (int)ms.Length);
            file.Write(bytes, 0, bytes.Length);
            ms.Close();
        }
    }
}

When I run it in .NET Core, I get the expected output:

Current framework is .NETCoreApp,Version=v7.0

written to: C:\temp\testClassNET.pbf testClass created by .NETCoreApp,Version=v7.0 aType=System.Int64 types=System.Int32,System.Char,

read from: C:\temp\testClassNetFramework.pbf testClass created by .NETFramework,Version=v4.7.2 aType=System.Int64 types=System.Int32,System.Char,

read from: C:\temp\testClassNET.pbf testClass created by .NETCoreApp,Version=v7.0 aType=System.Int64 types=System.Int32,System.Char,

But when I run it on the full .NET Framework, the Types are deserialized as nulls - only from the file saved with .NET Core.

Current framework is .NETFramework,Version=v4.7.2

written to: C:\temp\testClassNetFramework.pbf testClass created by .NETFramework,Version=v4.7.2 aType=System.Int64      types=System.Int32,System.Char,

read from: C:\temp\testClassNetFramework.pbf testClass created by .NETFramework,Version=v4.7.2 aType=System.Int64    
types=System.Int32,System.Char,

read from: C:\temp\testClassNET.pbf testClass created by .NETCoreApp,Version=v7.0 aType= types=null,null,

(You'll need to run it once in each framework to generate the files first).

Why aren't the Types being deserialized correctly?


Solution

  • The Type class is really a base class that is designed to be inherited, and you don't really create instances of it, so there is no reason to serialize it. (I'm sure there are reasons to serialize an abstract class, but Type is more of a static definition). See this SO answer, here and here.

    Not sure how your testClass is defined, but if it looks something like this, then just serialize to a string instead:

    testClass make()
    {
        var tc = new testClass();
        tc.frameworkWhenSaved = framework;
        tc.aType = typeof(long).FullName;
        tc.types.Add(typeof(int).FullName);
        tc.types.Add(typeof(char).FullName);
        return tc;
    }
    
    
    [ProtoContract]
    public class testClass
    {
        public testClass()
        {
            types = new List<string>();
        }
        [ProtoMember(1)]
        public string frameworkWhenSaved { get; set; }
        [ProtoMember(2)]
        public string aType { get; set; }
        [ProtoMember(3)]
        public List<string> types { get; set; } 
    }
    

    and protobuf-net can correctly deserialize to this class:

    [ProtoContract]
    public class testClass
    {
        public testClass()
        {
            types = new List<Type>();
        }
        [ProtoMember(1)]
        public string frameworkWhenSaved { get; set; }
        [ProtoMember(2)]
        public Type aType { get; set; }
        [ProtoMember(3)]
        public List<Type> types { get; set; }
    }