【问题标题】:C#: Printing all properties of an object [duplicate]C#:打印对象的所有属性[重复]
【发布时间】:2010-10-25 12:42:11
【问题描述】:

.NET 中是否有一种方法可以将对象的所有属性等写入控制台?

当然可以使用反射,但我很好奇这是否已经存在......尤其是因为您可以在 Visual Studio 中的即时窗口中做到这一点。在那里你可以输入一个对象名称(在调试模式下),按回车键,它的所有内容都会被打印得相当漂亮。

这样的方法存在吗?

【问题讨论】:

标签: c# object serialization console


【解决方案1】:

您可以使用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 命名空间中,是 Visual Studio 用于在其属性浏览器中显示对象的 API。它最终基于反射(就像任何解决方案一样),但它从反射 API 提供了相当好的抽象级别。

【讨论】:

  • 酷!不知道那件事。与使用 obj.GetType().GetProperties() 和 GetValue 和 SetValue 相比,如何使用这个 PropertyDescriptor 和 GetValue?只是“界面”不同而已吗?
  • 它是反射 API 之上的高级 API。它旨在以用户友好的方式显示属性。 PropertyDescriptor 类有多种方法可以让您轻松编辑、更改和重置属性值,如果您愿意的话。
  • 在你的答案中提到命名空间做得很好!
  • 如果您有一个具有嵌套值的复杂对象,则此答案完全没用。这只持续到 1 级,它根本不会更进一步......
  • @Best_Where_Gives - 所以你可以扩展代码来处理这个问题,在 engineforce 已经完成了。有时您必须自己编写一些代码..!
【解决方案2】:

跟随 sn -p 将执行所需的功能:

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.GetValue(obj));
}

我认为如果你把它写成扩展方法,你可以在所有类型的对象上使用它。

【讨论】:

  • 这不会处理由其他对象组成的对象。它也不输出属性的值。只有名字。我已经知道了:P
  • @Svish 但你的问题根本没有给出这个想法。请编辑。
  • @nawfal 其他人似乎理解得很好。
  • 类型 t = typeof(T); foreach (var p in t.GetProperties()) { System.Console.WriteLine(p.Name + " " + p.GetType().ToString()); }
【解决方案3】:

众所周知,ObjectDumper 类可以做到这一点。我从未确认过,但我一直怀疑即时窗口使用它。

编辑:我刚刚意识到,ObjectDumper 的代码实际上在您的机器上。前往:

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

这将解压缩到一个名为 LinqSamples 的文件夹。在那里,有一个名为 ObjectDumper 的项目。使用它。

【讨论】:

  • 哇,这完全有效。尽管有一些深度控制会很好,哈哈。感谢您的精彩提示! =)
  • 查看我的编辑。样本中的那个实际上有一个需要深度的重载。
  • 嗯,是我一个人,还是把所有的东西都输出在一行上?
  • 这可能很明显,但 VS2010 用户(很可能)会在此处找到它:C:\Program Files (x86)\Microsoft Visual Studio 10.0\Samples\1033
  • nuget package for ObjectDumper 现在可用。它还提供了DumpToStringDumpObject 类的扩展方法。方便。
【解决方案4】:

基于 LINQ 示例的 ObjectDumper,我创建了一个版本,将每个属性转储到自己的行中。

这个类示例

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 存储在列表中。
  • AlreadyTouched 已修复(参见 cmets)
  • FormatValue 已修复(参见 cmets)

【讨论】:

  • 小心这一点,如果你有双向对象引用,你可能会遇到 stackoverflow 异常
  • 为什么要使用哈希?引用完整性还不够吗?
  • 可能想要设置一个最大级别(可能不希望深入 10 个对象),如果元素是流,这将引发异常
  • 如果对象为空,AlreadyTouched 会引发异常。您需要将if (value == null) return false; 添加到此方法的开头。
  • 我对您的代码进行了一些更改并将其放在github.com/mcshaz/BlowTrial/blob/master/GenericToDataFile/… 上 - 这可以处理更复杂的对象,包括递归深度,跳过索引器和不带 getter 的属性,并避免对 StringBuiler ToString 的不必要调用。它还将“双向引用”消息更改为“引用已转储”,因为在哈希表中找到的引用并不总是双向的(例如重复相同实例的列表)。
【解决方案5】:

不这么认为。我总是不得不编写它们或使用其他人的工作来获取这些信息。据我所知,必须是反思。

编辑:
检查this out。我正在调查对长对象图的一些调试,并在我添加手表时注意到了这一点,VS 在这个类中抛出:Mscorlib_CollectionDebugView&lt;&gt;。它是一种内部类型,可以很好地显示集合,以便在监视窗口/代码调试模式下查看。现在因为它是内部的,您可以引用它,但您可以使用 Reflector 复制(来自 mscorlib)代码并拥有自己的代码(上面的链接有一个复制/粘贴示例)。看起来真的很有用。

【讨论】:

    【解决方案6】:

    关于 Sean 的回复中的 TypeDescriptor(我无法发表评论,因为我的名声不好)...使用 TypeDescriptor 而不是 GetProperties() 的一个优点是 TypeDescriptor 具有在运行时动态将属性附加到对象和正常反射的机制会错过这些。

    例如,当使用 PowerShell 的 PSObject(可以在运行时添加属性和方法)时,他们实现了一个自定义 TypeDescriptor,它将这些成员与标准成员集合并。通过使用 TypeDescriptor,您的代码无需了解这一事实。

    组件、控件,我想也许 DataSets 也使用这个 API。

    【讨论】:

      【解决方案7】:

      任何其他解决方案/库最终都会使用反射来内省类型...

      【讨论】:

      • 当然可以,但如果不需要的话,我还是不想自己编写代码;)
      【解决方案8】:

      这正是反射的用途。我认为没有更简单的解决方案,但无论如何反射并不是代码密集型的。

      【讨论】:

        【解决方案9】:

        【讨论】:

        • 有趣...你会怎么用它?
        • 你需要整理 JSON blob 以便它像样的......我会说它需要与编写你自己的反射代码一样多的行。但那是我的 2c。
        • 好点cottsak。现在想通了如何使用它,尽管所有数据似乎都在那里,但开箱即用并不是很可读=)
        • 这很好用;我使用JSON formatter 使其可读。
        • 这是最好的答案。使用经过验证的框架,而不是推出自己的代码。
        猜你喜欢
        • 2013-07-11
        • 1970-01-01
        • 2018-12-28
        • 2011-03-03
        • 2018-06-28
        • 2011-08-23
        • 2019-01-31
        • 2023-03-27
        • 2016-03-14
        相关资源
        最近更新 更多