С#: печать всех свойств объекта

Есть ли встроенный метод .NET, который может записывать все свойства и такой объект в консоль? Мог бы сделать это с использованием отражения, конечно, но мне любопытно, если это уже существует... тем более, что вы можете сделать это в Visual Studio в окне Immediate. Там вы можете указать имя объекта (в режиме отладки), нажать enter и напечатать его довольно красиво со всеми его материалами.

Существует ли такой метод?

Ответ 1

Известно, что класс ObjectDumper делает это. Я никогда не подтверждал, но я всегда подозревал, что это окно использует это окно.

EDIT: Я просто понял, что код для ObjectDumper на самом деле находится на вашей машине. Перейдите к:

c:/Программные файлы /Microsoft Visual Studio 9.0/Образцы/1033/CSharpSamples.zip

Это распакуется в папку LinqSamples. Там есть проект ObjectDumper. Используйте это.

(Это также сделает Дэвида в комментариях счастливым:))

Ответ 2

Для этого можно использовать класс TypeDescriptor:

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

TypeDescriptor живет в пространстве имен System.ComponentModel и является API, который Visual Studio использует для отображения вашего объекта в своем браузере свойств. Это в конечном счете основано на отражении (как и любое решение), но оно обеспечивает довольно хороший уровень абстракции от API отражения.

Ответ 3

На основе ObjectDumper образцов LINQ я создал версию, которая сбрасывает каждое из свойств в своей собственной строке.

Этот образец класса

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

имеет выход

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

Вот код.

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 ("{ }");
    }
}

и вы можете использовать его так:

var dump = ObjectDumper.Dump(user);

Изменить

  • Двунаправленные ссылки теперь прекращены. Поэтому HashCode объекта хранится в списке.
  • Уже исправлено (см. комментарии)
  • FormatValue исправлено (см. комментарии)

Ответ 5

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

Например, при работе с PowerShell PSObject, который может иметь свойства и методы, добавленные во время выполнения, они реализовали собственный TypeDescriptor, который объединяет эти элементы со стандартным набором элементов. Используя TypeDescriptor, ваш код не должен знать об этом факте.

Компоненты, элементы управления, и я думаю, что, возможно, DataSets также используют этот API.

Ответ 6

Следующий фрагмент сделает желаемую функцию:

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

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

Ответ 7

Это именно то, о чем идет речь. Я не думаю, что там есть более простое решение, но в любом случае отражение не настолько интенсивно для кода.

Ответ 8

Не думай так. Мне всегда приходилось писать их или использовать кто-то другой, чтобы получить эту информацию. Должен быть отражением, насколько мне известно.

EDIT:
Проверьте этот вариант. Я изучал некоторые отладки на длинных графах объектов и заметил это, когда я добавляю часы, VS бросает в этом классе: Mscorlib_CollectionDebugView<>. Это внутренний тип для удобного отображения коллекций для просмотра в режимах отладки окна/кода. Теперь coz это внутреннее вы можете ссылаться на него, но вы можете использовать Reflector для копирования (из mscorlib) кода и иметь свой собственный (ссылка выше имеет пример копирования/вставки). Выглядит очень полезно.

Ответ 9

Любое другое решение/библиотека в конце концов будет использовать отражение для интроспекции типа...