1

我正在检查 protobuf-net 是否可以替代 DataContracts。除了出色的性能外,它确实是一个整洁的库。我唯一的问题是 .NET 序列化程序不会对它们当前正在反序列化的内容做出任何假设。特别是包含对类型化对象的引用的对象是一个问题。

[DataMember(Order = 3)]
public object Tag1 // The DataContract did contain a object which becomes now a SimulatedObject
{
    get;
    set;
}

我尝试使用带有一点通用帮助器的协议缓冲区来模拟对象,该帮助器确实将每种可能的类型存储在不同的强类型字段中。

这是处理反/序列化为许多不同的不相关类型的字段的推荐方法吗?

下面是一个 SimulatedObject 的示例代码,它最多可以容纳 10 种不同的类型。

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using ProtoBuf;
using System.Diagnostics;

[DataContract]
public class SimulatedObject<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10>
{
    [DataMember(Order = 20)]
    byte FieldHasValue; // the number indicates which field actually has a value

    [DataMember(Order = 1)]
    T1 I1;

    [DataMember(Order = 2)]
    T2 I2;

    [DataMember(Order = 3)]
    T3 I3;

    [DataMember(Order = 4)]
    T4 I4;

    [DataMember(Order = 5)]
    T5 I5;

    [DataMember(Order = 6)]
    T6 I6;

    [DataMember(Order = 7)]
    T7 I7;

    [DataMember(Order = 8)]
    T8 I8;

    [DataMember(Order = 9)]
    T9 I9;

    [DataMember(Order = 10)]
    T10 I10;

    public object Data
    {
        get
        {
            switch(FieldHasValue)
            {
                case 0: return null;
                case 1: return I1;
                case 2: return I2;
                case 3: return I3;
                case 4: return I4;
                case 5: return I5;
                case 6: return I6;
                case 7: return I7;
                case 8: return I8;
                case 9: return I9;
                case 10: return I10;
                default:
                    throw new NotSupportedException(String.Format("The FieldHasValue field has an invlaid value {0}. This indicates corrupt data or incompatible data layout chagnes", FieldHasValue));
            }
        }
        set
        {
            I1 = default(T1);
            I2 = default(T2);
            I3 = default(T3);
            I4 = default(T4);
            I5 = default(T5);
            I6 = default(T6);
            I7 = default(T7);
            I8 = default(T8);
            I9 = default(T9);
            I10 = default(T10);


            if (value != null)
            {
                Type t = value.GetType();
                if (t == typeof(T1))
                {
                    FieldHasValue = 1;
                    I1 = (T1) value;
                }
                else if (t == typeof(T2))
                {
                    FieldHasValue = 2;
                    I2 = (T2) value;
                }
                else if (t == typeof(T3))
                {
                    FieldHasValue = 3;
                    I3 = (T3) value;
                }
                else if (t == typeof(T4))
                {
                    FieldHasValue = 4;
                    I4 = (T4) value;
                }
                else if (t == typeof(T5))
                {
                    FieldHasValue = 5;
                    I5 = (T5) value;
                }
                else if (t == typeof(T6))
                {
                    FieldHasValue = 6;
                    I6 = (T6) value;
                }
                else if (t == typeof(T7))
                {
                    FieldHasValue = 7;
                    I7 = (T7) value;
                }
                else if (t == typeof(T8))
                {
                    FieldHasValue = 8;
                    I8 = (T8) value;
                }
                else if (t == typeof(T9))
                {
                    FieldHasValue = 9;
                    I9 = (T9) value;
                }
                else if (t == typeof(T10))
                {
                    FieldHasValue = 10;
                    I10 = (T10) value;
                }
                else
                {
                    throw new NotSupportedException(String.Format("The type {0} is not supported for serialization. Please add the type to the SimulatedObject generic argument list.", t.FullName));
                }
            }
        }
    }
}

[DataContract]
class Customer
{
    /* 
    [DataMember(Order = 3)]
    public object Tag1 // The DataContract did contain a object which becomes now a SimulatedObject
    {
        get;
        set;
    }
    */

    [DataMember(Order = 3)]
    public SimulatedObject<bool, Other, Other, Other, Other, Other, Other, Other, Other, SomethingDifferent> Tag1 // Can contain up to 10 different types
    {
        get;
        set;
    }



    [DataMember(Order = 4)]
    public List<string> Strings
    {
        get;
        set;
    }
}

[DataContract]
public class Other
{
    [DataMember(Order = 1)]
    public string OtherData
    {
        get;
        set;
    }
}

[DataContract]
public class SomethingDifferent
{
    [DataMember(Order = 1)]
    public string OtherData
    {
        get;
        set;
    }

}


class Program
{
    static void Main(string[] args)
    {
        Customer c = new Customer
        {
            Strings = new List<string> { "First", "Second", "Third" },
            Tag1 = new SimulatedObject<bool, Other, Other, Other, Other, Other, Other, Other, Other, SomethingDifferent>
                    {
                        Data = new Other {  OtherData = "String value "}
                    }
        };

        const int Runs = 1000 * 1000;
        var stream = new MemoryStream();

        var sw = Stopwatch.StartNew();

        Serializer.Serialize<Customer>(stream, c);
        sw = Stopwatch.StartNew();
        for (int i = 0; i < Runs; i++)
        {
            stream.Position = 0;
            stream.SetLength(0);
            Serializer.Serialize<Customer>(stream, c);
        }
        sw.Stop();
        Console.WriteLine("Data Size with Protocol buffer Serializer: {0}, {1} objects did take {2}s", stream.ToArray().Length, Runs, sw.Elapsed.TotalSeconds);

        stream.Position = 0;
        var newCustw = Serializer.Deserialize<Customer>(stream);

        sw = Stopwatch.StartNew();
        for (int i = 0; i < Runs; i++)
        {
            stream.Position = 0;
            var newCust = Serializer.Deserialize<Customer>(stream);
        }
        sw.Stop();
        Console.WriteLine("Read object with Protocol buffer deserializer: {0} objects did take {1}s", Runs, sw.Elapsed.TotalSeconds);

    }
}
4

3 回答 3

1

不,这种解决方案很难长期维护。

我建议你在序列化过程中将序列化类型的全名附加到序列化数据中,并在反序列化过程开始时读取类型名称(无需更改protobuf源代码)

作为旁注,您应该尽量避免在反序列化过程中混合对象类型。我假设您正在更新现有的 .net 应用程序并且无法重新设计它。

更新:示例代码

public byte[] Serialize(object myObject)
{
    using (var ms = new MemoryStream())
    {
        Type type = myObject.GetType();
        var id = System.Text.ASCIIEncoding.ASCII.GetBytes(type.FullName + '|');
        ms.Write(id, 0, id.Length);
        Serializer.Serialize(ms, myObject);
        var bytes = ms.ToArray();
        return bytes;
    }
}

public object Deserialize(byte[] serializedData)
{
    StringBuilder sb = new StringBuilder();
    using (var ms = new MemoryStream(serializedData))
    {
        while (true)
        {
            var currentChar = (char)ms.ReadByte();
            if (currentChar == '|')
            {
                break;
            }

            sb.Append(currentChar);
        }

        string typeName = sb.ToString();

        // assuming that the calling assembly contains the desired type.
        // You can include aditional assembly information if necessary
        Type deserializationType = Assembly.GetCallingAssembly().GetType(typeName);

        MethodInfo mi = typeof(Serializer).GetMethod("Deserialize");
        MethodInfo genericMethod = mi.MakeGenericMethod(new[] { deserializationType });
        return genericMethod.Invoke(null, new[] { ms });
    }
}
于 2012-11-05T16:23:36.260 回答
0

我现在正在做类似的事情,我已经提供了 lib 的第一个版本:http: //bitcare.codeplex.com/

当前版本还不支持泛型,但我计划在最近的时间添加它。我只在那里上传了源代码——当我准备好使用泛型时,我也准备了 bin 版本......

它假设双方(客户端和服务器)都知道他们序列化/反序列化的内容,因此没有任何理由在其中嵌入完整的元数据信息。由于这种序列化结果非常小,并且生成的序列化程序工作得非常快。它有数据字典,使用智能数据存储(简称只存储重要的位),并在必要时进行最终压缩。如果您需要它,请尝试它是否可以解决您的问题。

许可证是 GPL,但我会尽快将其更改为限制较少的许可证(也可免费用于商业用途,但风险自负,如在 GPL 中)

于 2012-11-12T14:44:25.843 回答
-1

我上传到 codeplex 的版本适用于我的一些产品。当然,它使用不同的单元测试集进行了测试。它们没有上传到那里,因为我将它移植到 vs2012 和 .net 4.5 并决定为即将发布的版本创建新的测试用例集。

我不处理抽象(所谓的开放)泛型。我处理参数化的泛型。从数据契约的角度来看,参数化的泛型只是专门的类,所以我可以像往常一样处理它们(与其他类一样)——区别仅在于对象构造和存储优化。

当我在 Nullable<> 上存储有关空值的信息时,它仅在存储流中占用一位,如果它不是空值,我会根据作为泛型参数提供的类型进行序列化(因此我对 DateTime 进行序列化,例如可以从一位获取对于所谓的默认值到几个字节)。目标是根据当前关于类上的数据契约的知识生成序列化代码,而不是动态地生成序列化代码并浪费内存和处理能力。当我在代码生成期间看到基于某个泛型的某个类中的属性时,我知道该泛型的所有属性,并且我知道每个属性的类型:) 从这个角度来看,它是具体的类。

我将尽快更改许可证。我必须首先弄清楚如何做:),因为我看到可以从提供的许可证类型列表中进行选择,但我无法提供自己的许可证文本。我看到 Newtonsoft.Json 的许可证也是我想要的,但我还不知道如何更改它......

那里还没有提供文档,但简而言之,准备自己的序列化测试很容易。您必须使用您想要存储/序列化有效方式的类型编译程序集,然后在您的序列化库中创建 *.tt 文件(例如对于人员类 - 它也检查依赖关系并为其他依赖类生成代码)并保存文件(当您保存它们时,它会生成与序列化库合作的所有代码)。您还可以在构建配置中创建任务,以在每次构建解决方案时从 tt 文件重新生成源代码(可能实体框架在构建过程中以类似的方式生成代码)。您现在可以编译您的序列化库并测量结果的性能和大小。

我的框架需要这个序列化库,以便有效地使用带有 Azure 表和 Blob 存储的实体,因此我计划很快完成初始版本......

于 2012-11-13T14:50:41.103 回答