226 votes

C# : Impression de toutes les propriétés d'un objet

Existe-t-il une méthode intégrée à .NET permettant d'écrire toutes les propriétés et autres d'un objet dans la console ?

On pourrait utiliser la réflexion bien sûr, mais je suis curieux de savoir si cela existe déjà... d'autant plus que vous pouvez le faire dans Visual Studio dans la fenêtre immédiate. Là, vous pouvez taper le nom d'un objet (en mode débogage), appuyer sur Entrée, et il est imprimé assez joliment avec tous ses éléments.

Une telle méthode existe-t-elle ?

7 votes

Les réponses à cette question sont meilleures que sur Quelle est la meilleure façon de vider des objets entiers dans un journal en C# ?

3 votes

La première réponse à la question "originale" renvoie à cette question. L'ordre est incorrect.

1 votes

Qu'est-ce que c'est que les dumpers d'objets et autres réponses de réflexion ici... un sérialiseur ne pourrait-il pas réaliser cela simplement ?

357voto

Sean Points 22088

Vous pouvez utiliser le TypeDescriptor pour ce faire :

foreach(PropertyDescriptor descriptor in TypeDescriptor.GetProperties(obj))
{
    string name = descriptor.Name;
    object value = descriptor.GetValue(obj);
    Console.WriteLine("{0}={1}", name, value);
}

TypeDescriptor vit dans le System.ComponentModel et est l'API que Visual Studio utilise pour afficher votre objet dans son navigateur de propriétés. En fin de compte, elle est basée sur la réflexion (comme toute solution le serait), mais elle fournit un assez bon niveau d'abstraction de l'API de réflexion.

0 votes

Cool ! Je ne le savais pas. Comment se passe l'utilisation de ce PropertyDescriptor et de GetValue, par rapport à l'utilisation de obj.GetType().GetProperties() et GetValue et SetValue ? Est-ce que c'est un peu la même chose, juste une "interface" différente ?

0 votes

C'est une API de haut niveau sur l'API de réflexion. Elle est axée sur l'affichage des propriétés de manière conviviale. La classe PropertyDescriptor dispose de plusieurs méthodes qui vous permettent d'éditer, de modifier et de réinitialiser facilement la valeur de la propriété, si vous le souhaitez.

0 votes

Si vous voulez filtrer les propriétés à valeur nulle et les classer par ordre alphabétique var type = obj.GetType() ; var props = type.GetProperties() ; var pairs = props.Where(x => x.GetValue(obj, null) != null) //Ne pas inclure les propriétés à valeur nulle. Select(x => x.Name + " : " + x.GetValue(obj, null).ToString().Trim()) .OrderBy(x => x, StringComparer.Ordinal).ToArray() ; //Commande par alphabet var qs = string.Join(", ", pairs) ; return qs.ToString() ;

114voto

ms007 Points 1186

Sur la base de l'ObjectDumper des exemples LINQ, j'ai créé une version qui vide chacune des propriétés sur sa propre ligne.

Cet échantillon de classe

namespace MyNamespace
{
    public class User
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public Address Address { get; set; }
        public IList<Hobby> Hobbies { get; set; }
    }

    public class Hobby
    {
        public string Name { get; set; }
    }

    public class Address
    {
        public string Street { get; set; }
        public int ZipCode { get; set; }
        public string City { get; set; }    
    }
}

a une sortie de

{MyNamespace.User}
  FirstName: "Arnold"
  LastName: "Schwarzenegger"
  Address: { }
    {MyNamespace.Address}
      Street: "6834 Hollywood Blvd"
      ZipCode: 90028
      City: "Hollywood"
  Hobbies: ...
    {MyNamespace.Hobby}
      Name: "body building"

Voici le code.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

public class ObjectDumper
{
    private int _level;
    private readonly int _indentSize;
    private readonly StringBuilder _stringBuilder;
    private readonly List<int> _hashListOfFoundElements;

    private ObjectDumper(int indentSize)
    {
        _indentSize = indentSize;
        _stringBuilder = new StringBuilder();
        _hashListOfFoundElements = new List<int>();
    }

    public static string Dump(object element)
    {
        return Dump(element, 2);
    }

    public static string Dump(object element, int indentSize)
    {
        var instance = new ObjectDumper(indentSize);
        return instance.DumpElement(element);
    }

    private string DumpElement(object element)
    {
        if (element == null || element is ValueType || element is string)
        {
            Write(FormatValue(element));
        }
        else
        {
            var objectType = element.GetType();
            if (!typeof(IEnumerable).IsAssignableFrom(objectType))
            {
                Write("{{{0}}}", objectType.FullName);
                _hashListOfFoundElements.Add(element.GetHashCode());
                _level++;
            }

            var enumerableElement = element as IEnumerable;
            if (enumerableElement != null)
            {
                foreach (object item in enumerableElement)
                {
                    if (item is IEnumerable && !(item is string))
                    {
                        _level++;
                        DumpElement(item);
                        _level--;
                    }
                    else
                    {
                        if (!AlreadyTouched(item))
                            DumpElement(item);
                        else
                            Write("{{{0}}} <-- bidirectional reference found", item.GetType().FullName);
                    }
                }
            }
            else
            {
                MemberInfo[] members = element.GetType().GetMembers(BindingFlags.Public | BindingFlags.Instance);
                foreach (var memberInfo in members)
                {
                    var fieldInfo = memberInfo as FieldInfo;
                    var propertyInfo = memberInfo as PropertyInfo;

                    if (fieldInfo == null && propertyInfo == null)
                        continue;

                    var type = fieldInfo != null ? fieldInfo.FieldType : propertyInfo.PropertyType;
                    object value = fieldInfo != null
                                       ? fieldInfo.GetValue(element)
                                       : propertyInfo.GetValue(element, null);

                    if (type.IsValueType || type == typeof(string))
                    {
                        Write("{0}: {1}", memberInfo.Name, FormatValue(value));
                    }
                    else
                    {
                        var isEnumerable = typeof(IEnumerable).IsAssignableFrom(type);
                        Write("{0}: {1}", memberInfo.Name, isEnumerable ? "..." : "{ }");

                        var alreadyTouched = !isEnumerable && AlreadyTouched(value);
                        _level++;
                        if (!alreadyTouched)
                            DumpElement(value);
                        else
                            Write("{{{0}}} <-- bidirectional reference found", value.GetType().FullName);
                        _level--;
                    }
                }
            }

            if (!typeof(IEnumerable).IsAssignableFrom(objectType))
            {
                _level--;
            }
        }

        return _stringBuilder.ToString();
    }

    private bool AlreadyTouched(object value)
    {
        if (value == null)
            return false;

        var hash = value.GetHashCode();
        for (var i = 0; i < _hashListOfFoundElements.Count; i++)
        {
            if (_hashListOfFoundElements[i] == hash)
                return true;
        }
        return false;
    }

    private void Write(string value, params object[] args)
    {
        var space = new string(' ', _level * _indentSize);

        if (args != null)
            value = string.Format(value, args);

        _stringBuilder.AppendLine(space + value);
    }

    private string FormatValue(object o)
    {
        if (o == null)
            return ("null");

        if (o is DateTime)
            return (((DateTime)o).ToShortDateString());

        if (o is string)
            return string.Format("\"{0}\"", o);

        if (o is char && (char)o == '\0') 
            return string.Empty; 

        if (o is ValueType)
            return (o.ToString());

        if (o is IEnumerable)
            return ("...");

        return ("{ }");
    }
}

et vous pouvez l'utiliser comme ça :

var dump = ObjectDumper.Dump(user);

Modifier

  • Les références bi-directionnelles sont maintenant arrêtées. Le HashCode d'un objet est donc stocké dans une liste.
  • Correction de AlreadyTouched (voir les commentaires)
  • FormatValue corrigé (voir commentaires)

1 votes

Attention, si vous avez des références d'objets bidirectionnelles, vous pouvez rencontrer une exception de débordement de pile.

0 votes

Pourquoi utiliser un hachage ? L'intégrité référentielle ne serait-elle pas suffisante ?

0 votes

Peut vouloir définir un niveau maximal (une profondeur de 10 objets n'est probablement pas souhaitable) et si l'élément est un flux, ceci lancera une exception.

73voto

BFree Points 46421

En ObjectDumper La classe est connue pour faire ça. Je n'ai jamais confirmé, mais j'ai toujours soupçonné que la fenêtre immédiate utilise cela.

EDIT : Je viens de réaliser que le code pour ObjectDumper est en fait sur votre machine. Allez-y :

C:/Program Files/Microsoft Visual Studio 9.0/Samples/1033/CSharpSamples.zip

Cela va dézipper dans un dossier appelé LinqSamples . Là-dedans, il y a un projet appelé ObjectDumper . Utilisez cela.

0 votes

Woah, ça a carrément marché. Bien qu'un contrôle de la profondeur aurait été sympa à avoir, haha. Merci pour ce super conseil ! =)

0 votes

Voir mon édition. Celui dans les échantillons a en fait une surcharge qui prend de la profondeur.

0 votes

Hm, c'est juste moi, ou est-ce que ça sort tout sur une seule ligne ?

31voto

Marc Gravell Points 482669

1 votes

Intéressant... comment utiliseriez-vous cela ?

0 votes

Il vous faudrait mettre de l'ordre dans le blob JSON pour qu'il soit présentable et je dirais que cela prendrait autant de lignes que d'écrire votre propre code de réflexion. Mais ce n'est que mon avis.

0 votes

Bon point cottsak. j'ai trouvé comment l'utiliser maintenant, et bien que toutes les données semblent être là, ce n'était pas très lisible hors de la boîte =)

8voto

Josh Points 38617

En ce qui concerne le TypeDescriptor de la réponse de Sean (je ne peux pas faire de commentaires parce que j'ai une mauvaise réputation)... l'un des avantages de l'utilisation de TypeDescriptor par rapport à GetProperties() est que TypeDescriptor dispose d'un mécanisme pour attacher dynamiquement des propriétés aux objets au moment de l'exécution et que la réflexion normale les manquera.

Par exemple, lorsqu'ils travaillent avec PSObject de PowerShell, auquel des propriétés et des méthodes peuvent être ajoutées au moment de l'exécution, ils ont mis en œuvre un TypeDescriptor personnalisé qui fusionne ces membres avec l'ensemble de membres standard. En utilisant TypeDescriptor, votre code n'a pas besoin d'être conscient de ce fait.

Les composants, les contrôles et, je pense, les DataSets font également appel à cette API.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X