Динамическая Сериализация Объекта - PullRequest
22 голосов
/ 16 июня 2010

Я попытался сериализовать класс DynamicObject с BinaryFormatter, но:

  • Выходной файл слишком большой, не совсем подходит для проводов
  • Циркулярные ссылки не обработаны (застрял при сериализации)

Поскольку сериализация 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);                 
        }
    }

}

(Iдумаю, я мог бы использовать 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 ипохоже, что исходные данные правильно десериализованы, хотя с указанными выше недостатками, если вы получаете творческий подход и перемещаете данные.

Я взглянул на protobuf-сеть Марка Грэйвела, но я не совсем уверен, какиспользуйте его в таком контексте (я даже не уверен, что выбрал правильную версию из репозитория, но эй).

Я знаю, что это больше, чем слова, но я не могу объяснитьсценарий лучше.Пожалуйста, скажите, могу ли я что-нибудь добавить, чтобы прояснить этот вопрос.

Любая помощь очень ценится.

Ответы [ 4 ]

12 голосов
/ 31 июля 2011

Я на 98% уверен, что эта последовательность будет работать для динамического объекта.

  1. преобразовать объект в объект Expando
  2. приведение объекта расширения к типу Словарь
  3. используйте ProtoBuf-net Serializer.Serialize / .Deserialize как обычно
  4. преобразовать словарь в Expando Object

Вы можете преобразовать объекты в набор пар имя / значение для передачи.

Это лишь небольшое подмножество того, что может сделать динамик, но, возможно, вам этого достаточно.

Существует некоторый пользовательский код для обработки некоторых приведенных выше преобразований, который я могу показать вам, если есть интерес.

У меня нет решения, когда динамический является заполнителем класса. Для этого случая я бы предложил получить тип и использовать оператор switch для сериализации / десериализации, как вам требуется. В этом последнем случае вам нужно было бы поместить что-то, чтобы указать, какой тип универсальной десериализации вам нужен (строка / id / полное имя типа / etc). Предполагается, что вы имеете дело со списком ожидаемых типов.

Примечание: Expando реализует IDictionary. Expando - это просто список пар ключ / значение. то есть. ключом является то, на что вы указываете, а значение - это возвращение из любой цепочки функций, реализующей это. Существует множество динамических интерфейсов для настройки синтаксического восприятия сахара, но большую часть времени вы будете смотреть на них.

рефов:

10 голосов
/ 28 июня 2010

Я не уверен, будет ли 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");
    }
1 голос
/ 25 апреля 2011

Прежде всего, размер вашего файла зависит от 2 вещей (если я понимаю, как работает BinaryFormatter, исправьте меня, если я ошибаюсь):

  1. Размер фактических значений, которые сериализуются, и
  2. Имена, используемые для сериализации значений объекта с помощью метода SerializationInfo.AddValue, которые хранятся в выходном файле, поэтому значения могут использоваться во время десериализации с тем же именем.

Очевидно, # 1 вызовет самое большое замедление, которое можно уменьшить только путем оптимизации объектов, которые вы пытаетесь сериализовать.

Поскольку вы используете динамические объекты, почти незаметно небольшое увеличение размера, вызванное # 2, неизбежно. Если вы знали типы и имена членов объекта заранее, вы могли бы просто дать каждому члену объекта очень короткое, последовательно определяемое имя («1», «2», «3» и т. Д.) Во время итерации. поверх членов объекта, добавляя их через SerializationInfo.AddValue. Затем, во время десериализации, вы можете использовать SerializationInfo.GetValue с тем же последовательно определяемым именем, и десериализация будет работать очень хорошо, независимо от фактических имен десериализованных значений, если вы выполняете итерацию в элементах объекта в том же порядке. они были добавлены. Конечно, это может сэкономить вам в среднем 4 или 5 байт на элемент, но эти небольшие суммы могут сложиться в большие объекты.

@ Рейн: (Полагаю, я мог бы использовать 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> в нем по отдельности. Это было бы бессмысленно, однако, с точки зрения оптимизации исходного кода, поскольку оно занимает столько же места на диске.

Подводя итог, я действительно не думаю, что есть какой-то способ оптимизировать сериализацию динамического объекта - вы должны циклически проходить по элементам объекта каждый раз, когда он сериализуется, и у вас нет возможности узнать размер объекта заранее (по определению динамический).

0 голосов
/ 14 апреля 2011

Я не знаю, поддерживает ли SharpSerializer динамические объекты, но стоит попробовать:

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

...