ericvana / protobuf-net

Automatically exported from code.google.com/p/protobuf-net
Other
0 stars 0 forks source link

Generic inheritance with custom type initialization give exception when type is first used in Deserialize() #404

Open GoogleCodeExporter opened 8 years ago

GoogleCodeExporter commented 8 years ago
Please include an e-mail address if this might need a dialogue!
==============
Please contact me at mennodeij[AT]gmail[DOT]com

What steps will reproduce the problem?
1. Compile the unit tests below and run the "TestFromColdString" as the first 
test - all subsequent tests will fail
2. If you run any of the other two tests first (for example 
"TestFromHotString") all tests will pass.
3. The error is due to the fact that the static class initializer is run inside 
the Deserialize method.

Is there another way to configure the generic inheritance in the class 
initializer so that this does not break?

What is the expected output? What do you see instead?
Expected output: all tests pass irrespective of the order in which they are 
run. Instead, if I run "TestFromColdString" as the first test that it and all 
other tests fail.

What version of the product are you using? On what operating system?
2.0.0.640/Win7

Please provide any additional information below.

using System;
using System.IO;
using NUnit.Framework;
using ProtoBuf;
using ProtoBuf.Meta;

namespace Protobuf.utest
{
    [ProtoContract]
    public class GenericBase<T, T2>
    {
        [ProtoMember(1)]
        public T2 Value2 { get; set; }

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

    [ProtoContract]
    public class GenericBase<T> : GenericBase<T, int>
    {

    }

    [ProtoContract]
    public class StringImpl : GenericBase<String>, IEquatable<StringImpl>
    {
        static StringImpl()
        {
            // this needs to be added so that the runtime type model 
            // is informed about the inheritance tree. If it is not present
            // the base class values are not deserialized.
            MetaType gb1 = RuntimeTypeModel.Default.Add(typeof(GenericBase<String, int>), true);
            MetaType gb2 = RuntimeTypeModel.Default.Add(typeof (GenericBase<String>), true);
            RuntimeTypeModel.Default.Add(typeof (StringImpl), true);
            gb1.AddSubType(100, typeof (GenericBase<String>));
            gb2.AddSubType(101, typeof (StringImpl));
        }

        [ProtoMember(3)]
        public String Name { get; set; }

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public bool Equals(StringImpl other)
        {
            return Value == other.Value && Value2 == other.Value2 && Name == other.Name;
        }
    }

    [TestFixture]
    public class ProtobufGenericTypeInitializer
    {
        [Test]
        public void TestGenericProtobuf()
        {
            StringImpl obj = new StringImpl {Name = "My Name", Value="My Value", Value2 = 10}; 
            Assert.That(obj.Equals(obj), Is.True); // Equals works

            MemoryStream ms = new MemoryStream();
            Serializer.Serialize(ms, obj);

            ms.Seek(0L, SeekOrigin.Begin);
            StringImpl obj2 = Serializer.Deserialize<StringImpl>(ms);

            Assert.That(obj.Equals(obj2), Is.True);

            Console.WriteLine(Convert.ToBase64String(ms.ToArray()));
        }

        [Test][ExpectedException(typeof(TypeInitializationException))]
        public void TestFromColdString()
        {
            // this test does a deserialization prior to the first object
            // construction - this leads to a TypeInitializationException

            // this is the base64-encoded protocul buffer of obj (see below)
            String base64 = "ogYMqgYJGgdNeSBOYW1lCAoSCE15IFZhbHVl"; 
            byte[] bytes = Convert.FromBase64String(base64);
            MemoryStream ms = new MemoryStream(bytes);

            ms.Seek(0L, SeekOrigin.Begin);
            StringImpl obj2 = Serializer.Deserialize<StringImpl>(ms);

            StringImpl obj = new StringImpl {Name = "My Name", Value="My Value", Value2 = 10}; 

            Assert.That(obj.Equals(obj2), Is.True);
        }

        [Test]
        public void TestFromHotString()
        {
            // this test does a deserialization after to the first object
            // construction - this gives no exceptions
            StringImpl obj = new StringImpl { Name = "My Name", Value = "My Value", Value2 = 10 };
            String base64 = "ogYMqgYJGgdNeSBOYW1lCAoSCE15IFZhbHVl"; // this is the base64-encoded protocul buffer of obj (see above)
            byte[] bytes = Convert.FromBase64String(base64);
            MemoryStream ms = new MemoryStream(bytes);

            ms.Seek(0L, SeekOrigin.Begin);
            StringImpl obj2 = Serializer.Deserialize<StringImpl>(ms);

            Assert.That(obj.Equals(obj2), Is.True);
        }
    }
}

Original issue reported on code.google.com by mennod...@gmail.com on 30 Aug 2013 at 11:44

GoogleCodeExporter commented 8 years ago
Ok, for what it's worth: running the following prior to any action will 
correctly initialize generic types as far as I can see.

        private void PopulateTypes(Type t)
        {
            foreach (object mt in RuntimeTypeModel.Default.GetTypes())
            {
                MetaType theType = mt as MetaType;
                if (null != theType)
                {
                    if (theType.Type == t)
                        return;
                }
            }

            Type objType = typeof(object);
            List<Type> inheritanceTree = new List<Type>();
            do
            {
                inheritanceTree.Insert(0, t);
                t = t.BaseType;
            } while (null != t && t != objType);

            if (!inheritanceTree.Any(gt => gt.IsGenericType))
                return;

            int n = 100;
            for (int i = 0; i < inheritanceTree.Count - 1; i++)
            {
                Type type = inheritanceTree[i];
                MetaType mt = RuntimeTypeModel.Default.Add(type, true);
                mt.AddSubType(n++, inheritanceTree[i + 1]);
            }
        }

Original comment by mennod...@gmail.com on 17 Sep 2013 at 9:59