【问题标题】:Dynamic Object Serialization动态对象序列化
【发布时间】:2011-03-04 14:00:59
【问题描述】:

我尝试用BinaryFormatter 序列化DynamicObject 类,但是:

  • 输出文件太大,不完全友好
  • 循环引用未处理(序列化时卡住)

由于 序列化 DynamicObject 本身意义不大,这是我尝试序列化的类:

[Serializable()]
class Entity
    : DynamicObject, ISerializable
{

    IDictionary<string, object> values = new Dictionary<string, object>();

    public Entity()
    {

    }

    protected Entity(SerializationInfo info, StreamingContext ctx)
    {
        string fieldName = string.Empty;
        object fieldValue = null;

        foreach (var field in info)
        {
            fieldName = field.Name;
            fieldValue = field.Value;

            if (string.IsNullOrWhiteSpace(fieldName))
                continue;

            if (fieldValue == null)
                continue;

            this.values.Add(fieldName, fieldValue);
        }

    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        this.values.TryGetValue(binder.Name, out result);

        return true;
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        this.values[binder.Name] = value;

        return true;
    }        

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {            
        foreach (var kvp in this.values)
        {
            info.AddValue(kvp.Key, kvp.Value);                 
        }
    }

}

(我想我可以使用 ExpandoObject,但那是另一回事了。)

这是一个简单的测试程序:

    static void Main(string[] args)
    {
        BinaryFormatter binFmt = new BinaryFormatter();

        dynamic obj = new Entity();
        dynamic subObj = new Entity();
        dynamic obj2 = null;

        obj.Value = 100;
        obj.Dictionary = new Dictionary<string, int>() { { "la la la", 1000 } };

        subObj.Value = 200;
        subObj.Name = "SubObject";

        obj.Child = subObj;

        using (var stream = new FileStream("test.txt", FileMode.OpenOrCreate))
        {
            binFmt.Serialize(stream, obj);                
        }

        using (var stream = new FileStream("test.txt", FileMode.Open))
        {
            try
            {
                obj2 = binFmt.Deserialize(stream);                    
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }                
        }

        Console.ReadLine();

    }

在这里和那里放置一些断点帮助我查看了 obj2 的内容,看起来原始数据已正确反序列化,但如果您富有想象力并四处移动数据,则会出现上述缺点。

我查看了 Marc Gravell 的 protobuf-net,但我不确定如何在这种情况下使用它(我什至不确定我是否从存储库中选择了正确的版本,但是嘿)。

我知道代码多于文字,但我认为我无法更好地解释这个场景。请务必告诉我是否可以添加一些内容以使这个问题更清楚。

非常感谢任何帮助。

【问题讨论】:

  • 供参考,protobuf-net 目前不支持dynamic。我建议转移到 DTO 层进行序列化。
  • @Marc - 谢谢,我会调查的。仍然欢迎其他建议。
  • 好吧,从长远来看,这是我计划在 protobuf-net 中支持的东西。但我现在不能承诺任何事情。
  • @Raine - 我想出了一个迟来的方法来使用 Marc 的 protobuf-net 处理动态。见下文。

标签: c# serialization dynamic protobuf-net


【解决方案1】:

序列化动态对象的一个​​建议是将它们转换为字符串然后序列化,然后您可以反序列化回您的对象,如果适用于您的情况。

【讨论】:

    【解决方案2】:

    我 98% 确定此序列适用于动态对象。

    1. 将对象转换为 Expando 对象
    2. 将 expando 对象转换为 Dictionary 类型
    3. 正常使用 ProtoBuf-net Serializer.Serialize / .Deserialize
    4. 将字典转换为 Expando 对象

    您可以将对象转换为名称/值对的集合以进行传输。

    这只是动态可以做的一小部分,但也许对你来说已经足够了。

    有一些自定义代码可以处理上面的一些转换,如果有兴趣,我可以向您展示。

    当动态是类的占位符时,我没有解决方案。对于这种情况,我建议获取类型并使用 switch 语句根据需要进行序列化/反序列化。对于最后一种情况,您需要放置一个东西来指示您需要哪种类型的通用反序列化(字符串/id/完全限定的类型名称/等)。假设您正在处理预期类型的​​列表。

    注意:Expando 实现了 IDictionary。 Expando 仅仅是键/值对的列表。 IE。你点进去的东西是关键,价值是从任何实现它的函数链的返回。有一组用于自定义语法糖体验的动态界面,但大多数时候您不会去看它们。

    参考:

    【讨论】:

    • @ErikE:我当时应该做一个例子。我已经为您提供了一些参考链接。希望这些帮助。
    • 这不起作用,因为 ProtoBuf-net 不会序列化对象类型的值。底层字典的类型为IDictionary&lt;string, object&gt;
    【解决方案3】:

    首先,文件的大小取决于两件事(如果我理解 BinaryFormatter 的工作原理,如果我错了,请纠正我):

    1. 被序列化的实际值的大小,以及
    2. 用于使用SerializationInfo.AddValue 方法序列化对象值的名称,这些名称存储在输出文件中,因此可以在反序列化期间使用相同名称的值。

    显然,#1 将导致您最大的减速,只能通过优化您尝试序列化的对象来减少。

    因为您使用的是动态对象,所以由#2 引起的几乎不引人注意的小尺寸增加是不可避免的。如果您提前知道对象成员的类型和名称,则可以在迭代时为对象的每个成员提供非常短的、按顺序确定的名称(“1”、“2”、“3”等)在对象的成员之上,通过SerializationInfo.AddValue 添加它们。然后,在反序列化期间,您可以使用具有相同顺序确定名称的SerializationInfo.GetValue,并且反序列化将正常工作,无论被反序列化的值的实际名称如何,只要您以相同的顺序遍历对象的成员它们被添加进来了。当然,这可能只会为每个成员平均节省 4 或 5 个字节,但这些小量可以加起来形成大型对象。

    @Raine:(我想我可以使用 ExpandoObject,但那是另一回事了。)

    不是这样;我将您的代码示例更改为使用ExpandoObject 而不是您的Entity 类,然后向我抛出了SerializationExceptionExpandoObject 没有用SerializableAttribute 标记,并且它没有适当的构造函数来反序列化或序列化。然而,这并不意味着如果你真的想不能使用 ExpandoObject:它实现了IDictionary&lt;string, object&gt;,而后者又实现了ICollection&lt;KeyValuePair&lt;string, object&gt;&gt;。因此,ExpandoObject 实例是KeyValuePair&lt;string, object&gt; 实例的集合,它们标记为可序列化。因此,您可以序列化 ExpandoObject,但必须将其转换为 ICollection&lt;KeyValuePair&lt;string, object&gt;&gt; 并单独序列化其中的每个 KeyValuePair&lt;string, object&gt;。但是,就优化原始代码示例而言,这毫无意义,因为它占用的文件空间一样多。

    总而言之,我真的不认为有任何方法可以优化动态对象的序列化 - 每次序列化时都必须遍历对象的成员,并且您无法事先知道对象的大小(根据定义动态)。

    【讨论】:

      【解决方案4】:

      我不知道 SharpSerializer 是否支持动态对象,但可能值得一试:

      http://www.sharpserializer.com/en/index.html

      【讨论】:

        【解决方案5】:

        我不确定 JSON 在您的场景中是否可以接受,但如果可以,我已经使用 Json.net (http://json.codeplex.com) 来序列化动态类型。它工作得很好,速度很快,而且输出很小。虽然 Json.net 不直接返回动态对象,但是很容易将 Json.Net 的反序列化输出转换为任何动态类型。在下面的示例中,我使用 ExpandoObject 作为我的动态类型。下面的代码是我在 Facebook Graph Toolkit 中使用的代码。原文出处见此链接:http://facebookgraphtoolkit.codeplex.com/SourceControl/changeset/view/48442#904504

        public static dynamic Convert(string s) {
                    object obj = Newtonsoft.Json.JsonConvert.DeserializeObject(s);
                    if (obj is string) {
                        return obj as string;
                    } else {
                        return ConvertJson((JToken)obj);
                    }
            }
        
            private static dynamic ConvertJson(JToken token) {
                // FROM : http://blog.petegoo.com/archive/2009/10/27/using-json.net-to-eval-json-into-a-dynamic-variable-in.aspx
                // Ideally in the future Json.Net will support dynamic and this can be eliminated.
                if (token is JValue) {
                    return ((JValue)token).Value;
                } else if (token is JObject) {
                    ExpandoObject expando = new ExpandoObject();
                    (from childToken in ((JToken)token) where childToken is JProperty select childToken as JProperty).ToList().ForEach(property => {
                        ((IDictionary<string, object>)expando).Add(property.Name, ConvertJson(property.Value));
                    });
                    return expando;
                } else if (token is JArray) {
                    List<ExpandoObject> items = new List<ExpandoObject>();
                    foreach (JToken arrayItem in ((JArray)token)) {
                        items.Add(ConvertJson(arrayItem));
                    }
                    return items;
                }
                throw new ArgumentException(string.Format("Unknown token type '{0}'", token.GetType()), "token");
            }
        

        【讨论】:

        • 谢谢,很有趣。我会尽快看看。我唯一担心的是它(据称)不会像二进制序列化那样高效。
        • 在对我的问题尝试了几种不同的解决方案之后,您的代码就是我需要的代码。因此,尽管您的回答可能对 OP 的帮助不如我,但我仍然很高兴找到它! :)
        猜你喜欢
        • 1970-01-01
        • 2011-11-22
        • 1970-01-01
        • 1970-01-01
        • 2023-04-10
        • 1970-01-01
        • 1970-01-01
        • 2021-04-18
        • 1970-01-01
        相关资源
        最近更新 更多