web-dev-qa-db-fra.com

Sérialisation dynamique des objets

J'ai essayé de sérialiser une classe DynamicObject avec BinaryFormatter, mais:

  • Le fichier de sortie est trop gros et n'est pas vraiment convivial
  • Références circulaires non gérées (bloquées lors de la sérialisation)

Puisque la sérialisation d'une DynamicObjectsignifie très peu par lui-même, voici la classe que j'ai essayé de sérialiser:

[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);                 
        }
    }

}

(J'imagine que j'aurais pu utiliser un ExpandoObject, mais c'est une autre histoire.)

Voici un programme de test simple:

    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();

    }

Mettre quelques points d'arrêt ici et là m'a aidé à jeter un coup d'œil au contenu de obj2 et il semble que les données d'origine soient correctement désérialisées, mais avec les inconvénients ci-dessus si vous faites preuve d'imagination et déplacez les données.

J'ai jeté un coup d'œil au protobuf-net de Marc Gravell, mais je ne sais pas trop comment l'utiliser dans un tel contexte (je ne suis même pas sûr d'avoir pris la bonne version du référentiel, mais bon).

Je sais que c'est plus du code que des mots, mais je ne pense pas pouvoir expliquer le scénario mieux. S'il vous plaît dites-moi s'il y a quelque chose que je peux ajouter pour clarifier cette question.

Toute aide est très appréciée.

21
Raine

Je suis 98% certain que cette séquence fonctionnera pour un objet dynamique.

  1. convertir un objet en objet Expando
  2. cast expando objet de type Dictionnaire
  3. utiliser Serializer.Serialize/.Deserialize de ProtoBuf-net comme d'habitude
  4. convertir un dictionnaire en objet Expando

Vous pouvez convertir des objets en une collection de paires nom/valeur à transférer.

Ce n'est qu'un petit sous-ensemble de ce que la dynamique peut faire, mais peut-être que cela vous suffit.

Il existe un code personnalisé pour gérer certaines des conversions ci-dessus que je peux vous montrer si cela vous intéresse.

Je n'ai pas de solution pour quand dynamique est un espace réservé à une classe. Dans ce cas, je suggérerais d’obtenir le type et d’utiliser une instruction switch pour sérialiser/désérialiser selon vos besoins. Dans ce dernier cas, vous devez placer un objet pour indiquer le type de désérialisation générique dont vous avez besoin (chaîne/id/nom de type complet/etc). L’hypothèse est que vous avez affaire à une liste de types attendus.

Remarque: Expando implémente IDictionary. Un Expando est simplement une liste de paires clé/valeur. c'est à dire. la chose dans laquelle vous pointez est la clé, et la valeur est le retour de la chaîne de fonctions implémentée. Il existe un ensemble d'interfaces dynamiques permettant de personnaliser l'expérience du sucre syntaxique, mais la plupart du temps, vous n'aurez pas à les examiner.

réfs:

12
sgtz

Je ne sais pas si JSON serait acceptable dans votre scénario, mais si c'est le cas, j'ai utilisé Json.net ( http://json.codeplex.com ) pour sérialiser un type dynamique. Cela fonctionne assez bien, il est rapide et la sortie est de petite taille. Bien que Json.net ne renvoie pas directement d’objets dynamiques, il est très facile de convertir la sortie désérialisée de Json.Net en n’importe quel type dynamique. Dans l'exemple ci-dessous, j'utilise ExpandoObject comme type dynamique. Le code ci-dessous est ce que j'ai utilisé dans la boîte à outils Graph Facebook. Voir le lien de la source originale: 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");
    }
9
Nathan Totten

Tout d’abord, la taille de votre fichier dépend de 2 choses (si je comprends comment fonctionne BinaryFormatter, corrigez-moi si je me trompe):

  1. La taille des valeurs réelles en cours de sérialisation, et
  2. Les noms utilisés pour sérialiser les valeurs de l'objet avec la méthode SerializationInfo.AddValue, qui sont stockés dans le fichier de sortie afin que les valeurs puissent être utilisées lors de la désérialisation sous le même nom.

De toute évidence, le n ° 1 va provoquer votre plus grand ralentissement, qui ne peut être réduit qu'en optimisant les objets que vous essayez de sérialiser.

Parce que vous utilisez des objets dynamiques, l’augmentation de taille presque imperceptible causée par le n ° 2 est inévitable. Si vous connaissez les types et les noms des membres de l'objet à l'avance, vous pouvez simplement attribuer à chaque membre de l'objet un nom très court, déterminé séquentiellement ("1", "2", "3", etc.) lors de votre itération. sur les membres de l'objet, en les ajoutant via SerializationInfo.AddValue. Ensuite, lors de la désérialisation, vous pouvez utiliser SerializationInfo.GetValue avec le même nom déterminé séquentiellement, et la désérialisation fonctionnerait parfaitement, quels que soient les noms réels des valeurs désérialisées, tant que vous parcourez les membres de l'objet dans le même ordre. Cela peut vous faire économiser en moyenne 4 ou 5 octets par membre, mais ces petites quantités peuvent s’additionner dans des objets volumineux.

@Raine: (j'imagine que j'aurais pu utiliser un ExpandoObject, mais c'est une autre histoire.)

Pas si; J'ai modifié votre exemple de code afin qu'il utilise ExpandoObject à la place de votre classe Entity et j'ai lancé une SerializationException. ExpandoObject n'est pas marqué avec une SerializableAttribute et il n'a pas les constructeurs appropriés pour être désérialisé ou sérialisé. Cependant, cela ne signifie pas que vous ne pouvez pas utilisez ExpandoObject si vous le souhaitez vraiment: il implémente IDictionary<string, object>, qui à son tour implémente ICollection<KeyValuePair<string, object>>. Ainsi, une instance ExpandoObject est une collection d'instances KeyValuePair<string, object>, qui sont marquées comme sérialisables. Vous pouvez donc sérialiser un ExpandoObject, mais vous devez le convertir en ICollection<KeyValuePair<string, object>> et sérialiser chaque KeyValuePair<string, object> individuellement. Cela n’a aucun intérêt, cependant, en termes d’optimisation de votre exemple de code original, car il prend tout autant d’espace fichier.

En résumé, je ne pense vraiment pas qu'il soit possible d'optimiser la sérialisation d'un objet dynamique; vous devez parcourir les membres de l'objet à chaque fois qu'il est sérialisé et vous n'avez aucun moyen de connaître la taille de l'objet à l'avance (par définition de dynamique). .

1
Michael Hoffmann

Je ne sais pas si SharpSerializer prend en charge les objets dynamiques, mais cela vaut la peine d'essayer:

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

0
Nikolai Sander