Quelle est la meilleure façon de vider des objets entiers dans un journal en C #?

129

Donc, pour afficher l'état d'un objet actuel au moment de l'exécution, j'aime vraiment ce que la fenêtre Visual Studio Immediate me donne. Faire juste un simple

? objectname

Me donnera un «vidage» bien formaté de l'objet.

Existe-t-il un moyen simple de le faire dans le code, afin que je puisse faire quelque chose de similaire lors de la journalisation?

Dan Esparza
la source
Au final, j'ai beaucoup utilisé T.Dump. C'est une solution assez solide - il suffit de faire attention à la récursivité.
Dan Esparza
C'est une vieille question, mais qui arrive en tête de nombreux résultats de recherche. Pour les futurs lecteurs: Voir cette vs l' extension . A très bien fonctionné pour moi dans VS2015.
Jesse Good
1
Mise à jour pour 2020 car ce plugin VS n'est pas maintenu et manque de certaines fonctionnalités. La bibliothèque suivante fait la même chose dans le code - et elle a quelques fonctionnalités supplémentaires, par exemple, elle suit où elle est déjà visitée pour éviter les boucles: github.com/thomasgalliker/ObjectDumper
Nick Westgate

Réponses:

55

Vous pouvez baser quelque chose sur le code ObjectDumper fourni avec les exemples Linq .
Jetez également un œil à la réponse à cette question connexe pour obtenir un échantillon.

Mike Scott
la source
5
Cela ne fonctionne pas non plus pour les tableaux (il affiche simplement le type et la longueur du tableau, mais n'imprime pas son contenu).
Konrad Morawski
5
Le package nuget pour ObjectDumper est maintenant disponible. Il fournit également une méthode d'extension DumpToStringet Dumpune Objectclasse. Pratique.
IsmailS
2
w3wp.exeplante quand j'essaye d'utiliser ObjectDumpercommeRequest.DumpToString("aaa");
Paul
60

Pour un graphe d'objets plus grand, je seconde l'utilisation de Json mais avec une stratégie légèrement différente. J'ai d'abord une classe statique qui est facile à appeler et avec une méthode statique qui encapsule la conversion Json (note: pourrait en faire une méthode d'extension).

using Newtonsoft.Json;

public static class F
{
    public static string Dump(object obj)
    {
        return JsonConvert.SerializeObject(obj);
    }
}

Puis dans votre Immediate Window,

var lookHere = F.Dump(myobj);

lookHere s'affichera automatiquement dans la Localsfenêtre précédée d'un $ ou vous pouvez y ajouter une montre. Sur le côté droit de la Valuecolonne dans l'inspecteur, il y a une loupe avec un curseur déroulant à côté. Choisissez le curseur de la liste déroulante et choisissez le visualiseur Json.

Capture d'écran de la fenêtre Locaux de Visual Studio 2013

J'utilise Visual Studio 2013.

Jason
la source
2
SerializeObj -> SerializeObject?
Wiseman
Brillant, merci. Je ne peux pas installer les outils de débogage pour Visual Studio sur mon serveur distant, et cette chose fonctionne extrêmement bien dans mon application mvc asp.net.
Liam Kernighan
1
Pour une mise en forme agréable, vous pouvez faire:Newtonsoft.Json.JsonConvert.SerializeObject(sampleData, Formatting.Indented)
Zorgarath
tellement plus facile que d'essayer de le faire à la main. Ça devient compliqué
ahong
26

Je suis certain qu'il existe de meilleures façons de faire cela, mais j'ai dans le passé utilisé une méthode semblable à la suivante pour sérialiser un objet dans une chaîne que je peux enregistrer:

  private string ObjectToXml(object output)
  {
     string objectAsXmlString;

     System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(output.GetType());
     using (System.IO.StringWriter sw = new System.IO.StringWriter())
     {
        try
        {
           xs.Serialize(sw, output);
           objectAsXmlString = sw.ToString();
        }
        catch (Exception ex)
        {
           objectAsXmlString = ex.ToString();
        }
     }

     return objectAsXmlString;
  }

Vous verrez que la méthode peut également renvoyer l'exception plutôt que l'objet sérialisé, vous voudrez donc vous assurer que les objets que vous souhaitez consigner sont sérialisables.

Bernhard Hofmann
la source
2
À la lumière des fonctionnalités ajoutées à C # après avoir répondu à la question, il peut être utile de souligner que cette implémentation fonctionne bien comme méthode d'extension. Si elle est appliquée à la classe Object et que vous référencez l'extension partout où vous en avez besoin, cela peut être un moyen pratique d'appeler la fonction.
Nikita G.
Je continue à obtenir de ceci: Failed to access type 'System.__ComObject' failed. Noob to c #, apprécierait de l'aide.
GuySoft
1
@GuySoft Je soupçonne qu'une des propriétés de votre objet, ou l'objet lui-même, n'est pas sérialisable.
Bernhard Hofmann
Vous ne pouvez malheureusement pas utiliser cette méthode sur des classes sans constructeur sans paramètre. Ils ne sont pas sérialisables.
Jarekczek
22

Vous pouvez utiliser la fenêtre immédiate de Visual Studio

Collez simplement ceci (changez actualévidemment le nom de votre objet):

Newtonsoft.Json.JsonConvert.SerializeObject(actual);

Il devrait imprimer l'objet en JSON entrez la description de l'image ici

Vous devriez pouvoir le copier sur l'outil de texte textmechanic ou notepad ++ et remplacer les guillemets échappés ( \") par "et newlines ( \r\n) par un espace vide, puis supprimer les guillemets doubles ( ") du début et de la fin et le coller dans jsbeautifier pour le rendre plus lisible.

MISE À JOUR du commentaire d'OP

public static class Dumper
{
    public static void Dump(this object obj)
    {
        Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(obj)); // your logger
    }
}

cela devrait vous permettre de vider n'importe quel objet.

J'espère que cela vous fera gagner du temps.

Matas Vaitkevicius
la source
Merci. Vous ne l'avez peut-être pas compris dans ma question initiale, mais j'ai indiqué que je connaissais déjà la fenêtre immédiate et que je voulais faire la même chose lors de la connexion à mon application.
Dan Esparza
@DanEsparza Console.Log(Newtonsoft.Json.JsonConvert.SerializeObject(actual));? :) et oui je l'ai manqué. Cette question se pose lorsque vous recherchez google.co.uk/...
Matas Vaitkevicius
2
Pour info, lorsque vous avez une chaîne JSON dans une chaîne C #, cliquez sur l'icône de la lunette espion à droite de la chaîne et choisissez le visualiseur de texte. Il fera apparaître une fenêtre qui affiche une version en texte brut de la chaîne JSON (pas de guillemets ou \ r \ n).
Walter
17

ServiceStack.Text a une méthode d'extension T.Dump () qui fait exactement cela, vide récursivement toutes les propriétés de n'importe quel type dans un bon format lisible.

Exemple d'utilisation:

var model = new TestModel();
Console.WriteLine(model.Dump());

et sortie:

{
    Int: 1,
    String: One,
    DateTime: 2010-04-11,
    Guid: c050437f6fcd46be9b2d0806a0860b3e,
    EmptyIntList: [],
    IntList:
    [
        1,
        2,
        3
    ],
    StringList:
    [
        one,
        two,
        three
    ],
    StringIntMap:
    {
        a: 1,
        b: 2,
        c: 3
    }
}
Mythz
la source
1
Cela ne fonctionne pas pour les champs. Le PO demandait explicitement des "objets entiers".
Konrad Morawski
5
He didn't say fields- dit-il entire objects, qui comprend les champs. Il a également mentionné la fonctionnalité Fenêtre immédiate de Visual Studio comme exemple de ce qu'il voulait réaliser ( «Faire un simple ? objectnameme donnera un 'vidage' bien formaté de l'objet» ). ? objectnameimprime également tous les champs. This has been immensely helpful - one of my most used extension methods to date- Je ne mets pas en doute que c'est utile, mais seulement qu'il vide des objets entiers.
Konrad Morawski
3
@KonradMorawski Des objets entiers incorrects signifie un vidage récursif de l'objet, PAS qu'il inclut des champs, ce qui peut facilement conduire à une boucle récursive infinie. Vous ne devriez pas supposer ce que les autres impliquent. Ma réponse est à la fois pertinente et utile, votre vote négatif + commentaire ne l'est pas.
mythz
1
@mythz oui bien sûr, vous devez empêcher un débordement de pile (par exemple, chaque Int32champ a un MaxValuechamp, qui est un Int32lui-même ...), c'est un bon point, mais cela ne change pas le fait que les objets - et certainement ceux entiers - se composent également de champs, pas seulement de propriétés. De plus (vous ne l' avez pas l' adresse que l' on), ? objectnamedans les Immediate Window ne champs d'affichage - sans déclencher une boucle infinie. S'il s'agit de mon vote défavorable, je peux le retirer (si vous me le permettez en le déverrouillant, c'est-à-dire). Je ne suis pas d’accord en principe de toute façon.
Konrad Morawski
4
-1 pour essentiellement une réponse de lien uniquement, bien que cela ait l'air génial si je pouvais l'utiliser! Je suis peut-être aveugle, mais je ne trouve pas la source via ce lien; les deux dossiers de téléchargement sont vides. Le code est-il trop long à inclure dans la réponse?
14

Voici une façon stupidement simple d'écrire un objet plat, joliment formaté:

using Newtonsoft.Json.Linq;

Debug.WriteLine("The object is " + JObject.FromObject(theObjectToDump).ToString());

Ce qui se passe, c'est que l'objet est d'abord converti en une représentation interne JSON par JObject.FromObject, puis converti en chaîne JSON par ToString. (Et bien sûr, une chaîne JSON est une très belle représentation d'un objet simple, d'autant plus ToStringqu'elle inclura des retours à la ligne et des retraits.) Le "ToString" est bien sûr étranger (comme il est impliqué en utilisant +pour concaténer une chaîne et un objet), mais J'aime un peu le spécifier ici.

Hot Licks
la source
5
JsonConvert.SerializeObject (apprec, Formatting.Indented) pour une lecture confortable dans le journal
Tertium
1
HotLicks - Je veux vous dire à quel point cette contribution est importante pour moi en ce moment. J'ai l'obligation de fournir un audit de ce qui a changé pendant une mise à jour et vous venez de ramener mon stress du niveau de «panique» à un niveau gérable de «souci». Merci monsieur, puissiez-vous avoir une vie très longue et bénie
Iofacture
4

Vous pouvez utiliser la réflexion et parcourir toutes les propriétés de l'objet, puis obtenir leurs valeurs et les enregistrer dans le journal. Le formatage est vraiment trivial (vous pouvez utiliser \ t pour indenter les propriétés d'un objet et ses valeurs):

MyObject
    Property1 = value
    Property2 = value2
    OtherObject
       OtherProperty = value ...
Ricardo Villamil
la source
4

Ce que j'aime faire, c'est remplacer ToString () afin d'obtenir une sortie plus utile au-delà du nom du type. Ceci est pratique dans le débogueur, vous pouvez voir les informations souhaitées sur un objet sans avoir besoin de le développer.

Darryl Braaten
la source
3

J'ai trouvé une bibliothèque appelée ObjectPrinter qui permet de vider facilement des objets et des collections dans des chaînes (et plus). Il fait exactement ce dont j'avais besoin.

Marek Dzikiewicz
la source
3

Voici une autre version qui fait la même chose (et gère les propriétés imbriquées), ce que je pense est plus simple (pas de dépendances sur les bibliothèques externes et peut être modifiée facilement pour faire des choses autres que la journalisation):

public class ObjectDumper
{
    public static string Dump(object obj)
    {
        return new ObjectDumper().DumpObject(obj);
    }

    StringBuilder _dumpBuilder = new StringBuilder();

    string DumpObject(object obj)
    {
        DumpObject(obj, 0);
        return _dumpBuilder.ToString();
    }

    void DumpObject(object obj, int nestingLevel = 0)
    {
        var nestingSpaces = "".PadLeft(nestingLevel * 4);

        if (obj == null)
        {
            _dumpBuilder.AppendFormat("{0}null\n", nestingSpaces);
        }
        else if (obj is string || obj.GetType().IsPrimitive)
        {
            _dumpBuilder.AppendFormat("{0}{1}\n", nestingSpaces, obj);
        }
        else if (ImplementsDictionary(obj.GetType()))
        {
            using (var e = ((dynamic)obj).GetEnumerator())
            {
                var enumerator = (IEnumerator)e;
                while (enumerator.MoveNext())
                {
                    dynamic p = enumerator.Current;

                    var key = p.Key;
                    var value = p.Value;
                    _dumpBuilder.AppendFormat("{0}{1} ({2})\n", nestingSpaces, key, value != null ? value.GetType().ToString() : "<null>");
                    DumpObject(value, nestingLevel + 1);
                }
            }
        }
        else if (obj is IEnumerable)
        {
            foreach (dynamic p in obj as IEnumerable)
            {
                DumpObject(p, nestingLevel);
            }
        }
        else
        {
            foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(obj))
            {
                string name = descriptor.Name;
                object value = descriptor.GetValue(obj);

                _dumpBuilder.AppendFormat("{0}{1} ({2})\n", nestingSpaces, name, value != null ? value.GetType().ToString() : "<null>");
                DumpObject(value, nestingLevel + 1);
            }
        }
    }

    bool ImplementsDictionary(Type t)
    {
        return t.GetInterfaces().Any(i => i.Name.Contains("IDictionary"));
    }
}
force motrice
la source
1
cela mourra horriblement si vous avez une Datepropriété dans votre objet intérieur ... il suffit de dire ...
Noctis
2

Vous pouvez écrire votre propre méthode WriteLine-

public static void WriteLine<T>(T obj)
    {
        var t = typeof(T);
        var props = t.GetProperties();
        StringBuilder sb = new StringBuilder();
        foreach (var item in props)
        {
            sb.Append($"{item.Name}:{item.GetValue(obj,null)}; ");
        }
        sb.AppendLine();
        Console.WriteLine(sb.ToString());
    }

Utilisez-le comme-

WriteLine(myObject);

Pour écrire une collection, nous pouvons utiliser-

 var ifaces = t.GetInterfaces();
        if (ifaces.Any(o => o.Name.StartsWith("ICollection")))
        {

            dynamic lst = t.GetMethod("GetEnumerator").Invoke(obj, null);
            while (lst.MoveNext())
            {
                WriteLine(lst.Current);
            }
        }   

La méthode peut ressembler à -

 public static void WriteLine<T>(T obj)
    {
        var t = typeof(T);
        var ifaces = t.GetInterfaces();
        if (ifaces.Any(o => o.Name.StartsWith("ICollection")))
        {

            dynamic lst = t.GetMethod("GetEnumerator").Invoke(obj, null);
            while (lst.MoveNext())
            {
                WriteLine(lst.Current);
            }
        }            
        else if (t.GetProperties().Any())
        {
            var props = t.GetProperties();
            StringBuilder sb = new StringBuilder();
            foreach (var item in props)
            {
                sb.Append($"{item.Name}:{item.GetValue(obj, null)}; ");
            }
            sb.AppendLine();
            Console.WriteLine(sb.ToString());
        }
    }

En utilisant if, else ifet en vérifiant les interfaces, les attributs, le type de base, etc. et la récursivité (car c'est une méthode récursive) de cette façon, nous pouvons réaliser un vidage d'objets, mais c'est certainement fastidieux. L'utilisation du videur d'objets de l'exemple LINQ de Microsoft vous fera gagner du temps.

Islam Ariful
la source
Par curiosité: comment cela gère-t-il les tableaux ou les listes? Ou des propriétés qui font référence aux objets parents?
Dan Esparza
@DanEsparza Merci de me montrer le moyen d'être plus précis.
Ariful Islam
2

Sur la base de la réponse @engineforce, j'ai créé cette classe que j'utilise dans un projet PCL d'une solution Xamarin:

/// <summary>
/// Based on: https://stackoverflow.com/a/42264037/6155481
/// </summary>
public class ObjectDumper
{
    public static string Dump(object obj)
    {
        return new ObjectDumper().DumpObject(obj);
    }

    StringBuilder _dumpBuilder = new StringBuilder();

    string DumpObject(object obj)
    {
        DumpObject(obj, 0);
        return _dumpBuilder.ToString();
    }

    void DumpObject(object obj, int nestingLevel)
    {
        var nestingSpaces = "".PadLeft(nestingLevel * 4);

        if (obj == null)
        {
            _dumpBuilder.AppendFormat("{0}null\n", nestingSpaces);
        }
        else if (obj is string || obj.GetType().GetTypeInfo().IsPrimitive || obj.GetType().GetTypeInfo().IsEnum)
        {
            _dumpBuilder.AppendFormat("{0}{1}\n", nestingSpaces, obj);
        }
        else if (ImplementsDictionary(obj.GetType()))
        {
            using (var e = ((dynamic)obj).GetEnumerator())
            {
                var enumerator = (IEnumerator)e;
                while (enumerator.MoveNext())
                {
                    dynamic p = enumerator.Current;

                    var key = p.Key;
                    var value = p.Value;
                    _dumpBuilder.AppendFormat("{0}{1} ({2})\n", nestingSpaces, key, value != null ? value.GetType().ToString() : "<null>");
                    DumpObject(value, nestingLevel + 1);
                }
            }
        }
        else if (obj is IEnumerable)
        {
            foreach (dynamic p in obj as IEnumerable)
            {
                DumpObject(p, nestingLevel);
            }
        }
        else
        {
            foreach (PropertyInfo descriptor in obj.GetType().GetRuntimeProperties())
            {
                string name = descriptor.Name;
                object value = descriptor.GetValue(obj);

                _dumpBuilder.AppendFormat("{0}{1} ({2})\n", nestingSpaces, name, value != null ? value.GetType().ToString() : "<null>");

                // TODO: Prevent recursion due to circular reference
                if (name == "Self" && HasBaseType(obj.GetType(), "NSObject"))
                {
                    // In ObjC I need to break the recursion when I find the Self property
                    // otherwise it will be an infinite recursion
                    Console.WriteLine($"Found Self! {obj.GetType()}");
                }
                else
                {
                    DumpObject(value, nestingLevel + 1);
                }
            }
        }
    }

    bool HasBaseType(Type type, string baseTypeName)
    {
        if (type == null) return false;

        string typeName = type.Name;

        if (baseTypeName == typeName) return true;

        return HasBaseType(type.GetTypeInfo().BaseType, baseTypeName);
    }

    bool ImplementsDictionary(Type t)
    {
        return t is IDictionary;
    }
}
gianlucaparadise
la source
0

Tous les chemins ci-dessus supposent que vos objets sont sérialisables en XML ou JSON,
ou vous devez implémenter votre propre solution.

Mais à la fin, vous arrivez toujours au point où vous devez résoudre des problèmes tels que

  • récursivité dans les objets
  • objets non sérialisables
  • des exceptions
  • ...

Plus journal que vous voulez plus d'informations:

  • quand l'événement s'est produit
  • pile d'appels
  • qui a lu
  • ce qui était dans la session Web
  • quelle adresse IP
  • URL
  • ...

Il existe la meilleure solution qui résout tout cela et bien plus encore.
Utilisez ce package Nuget : Desharp .
Pour tous les types d'applications - applications Web et de bureau .
Voir sa documentation Desharp Github . Il a de nombreuses options de configuration .

Appelez n'importe où:

Desharp.Debug.Log(anyException);
Desharp.Debug.Log(anyCustomValueObject);
Desharp.Debug.Log(anyNonserializableObject);
Desharp.Debug.Log(anyFunc);
Desharp.Debug.Log(anyFunc, Desharp.Level.EMERGENCY); // you can store into different files
  • il peut enregistrer le journal dans un joli HTML (ou au format TEXTE, configurable)
  • il est possible d'écrire en option dans le thread d'arrière-plan (configurable)
  • il a des options pour la profondeur maximale des objets et la longueur maximale des chaînes (configurable)
  • il utilise des boucles pour les objets itérables et une réflexion en arrière pour tout le reste, en
    fait pour tout ce que vous pouvez trouver dans l'environnement .NET .

Je crois que cela aidera.

Tom Flídr
la source