2010-06-16 121 views
21

我试图序列化DynamicObjectBinaryFormatter,但:动态对象序列化

  • 输出文件太大,不完全丝友好
  • 循环引用不处理(停留在序列化)

由于序列化一个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,但我不确定如何在这样的环境下使用它(我甚至不确定我是否从版本库中选择了正确的版本,但是嘿) 。

我知道它比单词更多的代码,但我不认为我可以更好地解释这种情况。请告诉我是否可以添加一些内容以使问题更清楚。

任何帮助,非常感谢。

+0

作为参考,* protobuf网*目前有没有支持'dynamic'。我建议移动到DTO层进行序列化。 – 2010-06-16 20:30:19

+0

@Marc - 谢谢,我会研究一下。仍然接受其他建议。 – Raine 2010-06-17 08:19:23

+0

好吧,长期来说,这是我计划在protobuf网支持的东西。但我目前无法承诺。 – 2010-06-17 09:50:59

回答

12

我98%肯定这个序列可以用于动态对象。

  1. 转换目的是在Expando对象
  2. 铸造的expando对象为类型字典的
  3. 使用protobuf网Serializer.Serialize/.Deserialize按正常
  4. 转换字典来EXPANDO对象

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

这只是动态能够做的一小部分,但对您而言也许就足够了。

有一些自定义代码可以处理上面的一些转换,我可以告诉你,如果有兴趣。

我没有解决什么时候dynamic是占位符的问题。对于这种情况,我建议获取类型并使用switch语句按照需要序列化/反序列化。对于最后一种情况,您需要放置一些东西来表明您需要哪种类型的通用反序列化(字符串/标识/完全限定类型名称/等)。假设是你正在处理预期类型的​​列表。

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

参:

+0

是的,一些代码会非常好! – ErikE 2014-08-22 03:19:40

+0

@ErikE:我当时应该做一个例子。我已经为你提供了一些参考链接。希望那些帮助。 – sgtz 2014-08-23 11:54:27

+0

这不起作用,因为ProtoBuf-net不会序列化对象类型的值。底层字典的类型是'IDictionary '。 – ChrisW 2016-08-25 13:17:20

9

我不确定JSON是否可以在你的Senario中被接受,但是如果是我用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"); 
    } 
+0

谢谢,有趣。我会尽快看看。我唯一担心的是它不会像二进制序列化那样有效(据说)。 – Raine 2010-06-28 21:34:20

+2

在对我的问题尝试了几种不同的解决方案后,您的代码就是完成我需要的代码。所以,虽然你的答案可能没有像我一样帮助OP,但我仍然很欣赏它的发现! :) – 2011-07-25 14:36:50

1

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

  1. 实际值的大小是串行并且
  2. 用于使用SerializationInfo.AddValue方法序列化对象值的名称,这些名称存储在输出文件中,因此在反序列化过程中可以使用具有相同名称的值。

很明显,#1会导致你的最大速度下降,这只能通过优化你试图序列化的对象来减少。

因为您使用的是动态物体,所以#2造成的几乎难以察觉的小尺寸增加是不可避免的。如果您事先知道对象成员的类型和名称,那么您可以在迭代时给对象的每个成员非常短,顺序确定的名称(“1”,“2”,“3”等)通过对象的成员,通过SerializationInfo.AddValue添加它们。然后,在反序列化过程中,可以使用具有相同顺序确定名称的SerializationInfo.GetValue,并且反序列化可以很好地工作,而不管被反序列化的值的实际名称是什么,只要您按照它们相同的顺序遍历对象的成员当然,这可能只会为每个成员节省平均4或5个字节,但这些小数量可以加在大对象中。

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

不可。我改变了你的代码示例,使用ExpandoObject而不是你的Entity类,并得到SerializationException扔给我。 ExpandoObject没有标记为SerializableAttribute,并且它没有适当的构造函数进行反序列化或序列化。但是,这并不意味着你不能使用如果你真的想要:使用ExpandoObject实现IDictionary<string, object>,它反过来实现ICollection<KeyValuePair<string, object>>。因此,ExpandoObject实例是KeyValuePair<string, object>实例的集合,其中标记为可序列化的。因此,您可以序列化一个ExpandoObject,但是您必须将其转换为ICollection<KeyValuePair<string, object>>并逐个序列化其中的每个KeyValuePair<string, object>。然而,就优化您的原始代码示例而言,这样做毫无意义,因为它只需要尽可能多的文件空间。总之,我真的不认为有什么办法可以优化序列化动态对象 - 每次序列化时都必须遍历对象的成员,并且事先没有办法知道对象的大小(通过动态定义)。