web-dev-qa-db-fra.com

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

Existe-t-il une méthode intégrée à .NET qui peut écrire toutes les propriétés et un objet sur la console? On pourrait bien sûr en faire une en utilisant la réflexion, mais je suis curieux de savoir si cela existe déjà ... surtout que vous pouvez le faire dans Visual Studio dans la fenêtre Immédiate. Vous pouvez y entrer un nom d’objet (en mode débogage), appuyer sur Entrée et l’imprimer assez joliment avec tout son contenu.

Une telle méthode existe-t-elle?

169
Svish

La classe ObjectDumper est connue pour le faire. Je n'ai jamais confirmé, mais j'ai toujours suspecté que la fenêtre immédiate l'utilise.

EDIT: Je viens de me rendre compte que le code pour ObjectDumper est en réalité sur votre machine. Aller à:

c:/Fichiers de programme/Microsoft Visual Studio 9.0/Exemples/1033/CSharpSamples.Zip

Cela va décompresser dans un dossier appelé LinqSamples. Il y a un projet appelé ObjectDumper. Utiliser ça.

(Cela rendra David heureux dans les commentaires :))

62
BFree

Vous pouvez utiliser la classe TypeDescriptor pour faire ceci:

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

TypeDescriptor réside dans l'espace de noms System.ComponentModel et constitue l'API utilisée par Visual Studio pour afficher votre objet dans son navigateur de propriétés. Elle repose finalement sur la réflexion (comme toute solution), mais elle fournit un assez bon niveau d'abstraction à partir de l'API de réflexion.

291
Sean

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

Cet exemple 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);

Éditer

  • Les références bidirectionnelles sont maintenant arrêtées. Par conséquent, le HashCode d'un objet est stocké dans une liste.
  • Déjà touché corrigé (voir commentaires)
  • FormatValue corrigé (voir les commentaires)
95
ms007
24
Marc Gravell

En ce qui concerne TypeDescriptor de la réponse de Sean (je ne peux pas commenter, car j'ai une mauvaise réputation) ... un des avantages de l'utilisation de TypeDescriptor par rapport à GetProperties () est que TypeDescriptor dispose d'un mécanisme permettant d'associer de manière dynamique des propriétés à des objets lors de l'exécution. .

Par exemple, lorsqu'ils travaillent avec le PSObject de PowerShell, qui peut avoir des propriétés et des méthodes ajoutées au moment de l'exécution, ils ont implémenté 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 utilisent également cette API.

6
Josh

L'extrait suivant remplit la fonction souhaitée:

Type t = obj.GetType(); // Where obj is object whose properties you need.
PropertyInfo [] pi = t.GetProperties();
foreach (PropertyInfo p in pi)
{
    System.Console.WriteLine(p.Name + " : " + p.GetType());
}

Je pense que si vous écrivez ceci comme méthode d'extension, vous pouvez l'utiliser sur tous les types d'objets.

5
TheVillageIdiot

C'est exactement ce que la réflexion est pour. Je ne pense pas qu'il existe une solution plus simple, mais la réflexion n'est de toute façon pas très gourmande en code.

1
Jon B

Toute autre solution/bibliothèque utilisera à la fin la réflexion pour introspecter le type ...

0
mP.

Ne crois pas. J'ai toujours dû les écrire ou utiliser le travail de quelqu'un d'autre pour obtenir ces informations. Doit être une réflexion pour autant que je sache.

EDIT:
Check this out . J'enquêtais sur le débogage sur les graphes d'objet longs et je l'ai remarqué lorsque j'ai ajouté des montres, VS jette dans cette classe: Mscorlib_CollectionDebugView<>. Il s'agit d'un type interne permettant d'afficher les collections de manière agréable dans les modes de débogage des fenêtres de surveillance/code. Maintenant, c’est interne, vous pouvez le référencer, mais vous pouvez utiliser Reflector pour copier (à partir de mscorlib) le code et le vôtre (le lien ci-dessus contient un exemple de copier/coller). Ça a l'air vraiment utile.

0
Matt Kocaj