【问题标题】:Recursively Get Properties & Child Properties Of A Class递归获取类的属性和子属性
【发布时间】:2013-12-12 20:57:42
【问题描述】:

我正在做类似Recursively Get Properties & Child Properties Of An Object 的事情,但我想递归地使用反射来获取每个属性。我从Recursively Print the properties 得到了代码。

代码的问题是:它只下降了一层,我想知道如何使用反射自动获取所有属性?我刚刚编写了以下示例容器代码:

public class Container
{
    public Bottle MyBottle { get; set; }
    public List<Address> Addresses { get; set; }

    public Container()
    {
        Address a = new Address();
        a.AddressLine1 = "1 Main St";
        a.AddressLine2 = "2 Main St";
        Addresses = new List<Address>();
        Addresses.Add(a);

        MyBottle = new Bottle();
        MyBottle.BottleName = "Big bottle";
        MyBottle.BottageAge = 2;
    }
}

public class Bottle
{
    public string BottleName { get; set; }
    public int BottageAge { get; set; }
}

public class Address
{
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
    public List<SpecialFolder> SpecialFolders { get; set; }

    public Address()
    {
        SpecialFolders = new List<SpecialFolder>();
        SpecialFolder sf = new SpecialFolder();
        sf.TemplateFolder = Environment.SpecialFolder.Templates.ToString();
        sf.UserFolder = Environment.SpecialFolder.UserProfile.ToString();
        SpecialFolders.Add(sf);
    }
}

public class SpecialFolder
{
    public string TemplateFolder { get; set; }
    public string UserFolder { get; set; }
}

在 Main 方法中:

static void Main(string[] args)
{
    Container c = new Container();
    PrintProperties(c);
}
public static void PrintProperties(object obj)
{
    PrintProperties(obj, 0);
}
public static void PrintProperties(object obj, int indent)
{

    if (obj == null) return;
    string indentString = new string(' ', indent);
    Type objType = obj.GetType();
    PropertyInfo[] properties = objType.GetProperties();
    foreach (PropertyInfo property in properties)
    {
        object propValue = property.GetValue(obj, null);
        if (property.PropertyType.Assembly == objType.Assembly)
        {
            Console.WriteLine("{0}{1}:", indentString, property.Name);

            PrintProperties(propValue, indent + 2);
        }
        else
        {
            Console.WriteLine("{0}{1}: {2}", indentString, property.Name, propValue);
        }
    }
}

我希望得到:

MyBottle:
      BottleName: Big bottle
      BottageAge: 2
Addresses:
      AddressLine1: 1 Main St
      AddressLine2: 2 Main St
      SpecialFolders:
            TemplateFolder: Templates
            UserFolder: UserProfile

我现在得到的结果:

MyBottle:
  BottleName: Big bottle
  BottageAge: 2
Addresses: System.Collections.Generic.List`1[TreeViewReflectionExample.Address]

有人可以帮助我使用 PrintProperties 方法吗?非常感谢。

【问题讨论】:

    标签: c# reflection recursion


    【解决方案1】:

    您的代码有两个问题:

    1. 由于条件if (property.PropertyType.Assembly == objType.Assembly),您将省略System.Collections,如List&lt;&gt;
    2. 你不会区别对待 propValue 是集合。因此它将打印List 属性,而不是其元素属性。

    例如,您可以将其更改为:

    public void PrintProperties(object obj, int indent)
    {    
        if (obj == null) return;
        string indentString = new string(' ', indent);
        Type objType = obj.GetType();
        PropertyInfo[] properties = objType.GetProperties();
        foreach (PropertyInfo property in properties)
        {
            object propValue = property.GetValue(obj, null);
            var elems = propValue as IList;
            if (elems != null)
            {
                foreach (var item in elems)
                {
                    PrintProperties(item, indent + 3);
                }
            }
            else
            {
                // This will not cut-off System.Collections because of the first check
                if (property.PropertyType.Assembly == objType.Assembly)
                {
                    Console.WriteLine("{0}{1}:", indentString, property.Name);
    
                    PrintProperties(propValue, indent + 2);
                }
                else
                {
                    Console.WriteLine("{0}{1}: {2}", indentString, property.Name, propValue);
                }
            }
        }
    }
    

    【讨论】:

    • 正是我想要的,不仅适用于这个例子,而且适用于我所拥有的所有其他案例
    • 这很有帮助。我做了一个小补充,以确保在调用 GetValue 之前可以读取该属性,以防止只写属性出现异常 - 只需将其包装为“if (property.CanRead) {...”。
    • C# 6 语法糖的好消息:if (propValue is IList elems) 工作并节省一行。
    • 我得到了System.Reflection.TargetParameterCountException这个设置:var person = new Person { Name = "Name", Vorname = "Firstname", Alter = 27, Adresse = new Adresse { Strasse = "Street", Ort = "Place" }, Hobbies = new List&lt;string&gt; { "Tennis", "Salsa", "blub" } };
    • 不错的代码,但它不打印内置类型列表,例如List&lt;int&gt;,还有Arrays
    【解决方案2】:

    您希望分别处理原始类型和字符串,并循环遍历可枚举而不是仅获取它们的 ToString() 值。所以你的代码可以更新为:

    public void PrintProperties(object obj, int indent)
    {
    
        if (obj == null) return;
        string indentString = new string(' ', indent);
        Type objType = obj.GetType();
        PropertyInfo[] properties = objType.GetProperties();
        foreach (PropertyInfo property in properties)
        {   
            object propValue = property.GetValue(obj, null);
            if(property.PropertyType.IsPrimitive || property.PropertyType == typeof(string))
                Console.WriteLine("{0}{1}: {2}", indentString, property.Name, propValue);
            else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
            {
                Console.WriteLine("{0}{1}:", indentString, property.Name);
                IEnumerable enumerable = (IEnumerable)propValue;
                foreach(object child in enumerable)
                    PrintProperties(child, indent + 2);
            }
            else 
            {
                Console.WriteLine("{0}{1}:", indentString, property.Name);
                PrintProperties(propValue, indent + 2);
            }
        }
    }
    

    【讨论】:

    • 这段代码非常适合这个例子,当我添加更多的属性和类时,我得到了无限循环。我正在寻找一种获取所有属性的通用方法。
    • 经过一些小的修改后,这段代码对我有用。
    【解决方案3】:

    根据 Konrad Kokosa 的回答:

    private string ObjectToString(object obj, int indent = 0)
    {
        if (obj is null)
        {
            return "";
        }
    
        var sb = new StringBuilder();
        string indentString = new string(' ', indent);
        Type objType = obj.GetType();
    
        foreach (PropertyInfo property in objType.GetProperties())
        {
            object propValue = property.GetValue(obj);
            var elems = propValue as IList;
    
            if (elems != null)
            {
                foreach (var item in elems)
                {
                    sb.Append($"{indentString}- {property.Name}\n");
                    sb.Append(ObjectToString(item, indent + 4));
                }
            }
            else if (property.Name != "ExtensionData")
            {
                sb.Append($"{indentString}- {property.Name}={propValue}\n");
    
                if (property.PropertyType.Assembly == objType.Assembly)
                {
                    sb.Append(ObjectToString(propValue, indent + 4));
                }
            }
        }
    
        return sb.ToString();
    }
    

    更新

    根据这个较早的问题编辑代码:TargetParameterCountException when enumerating through properties of string

    private string ObjectToString(object obj, int indent = 0)
        {
            var sb = new StringBuilder();
    
            if (obj != null)
            {
                string indentString = new string(' ', indent);
    
                if (obj is string)
                {
                    sb.Append($"{indentString}- {obj}\n");
                }
                else if (obj is Array)
                {
                    var elems = obj as IList;
                    sb.Append($"{indentString}- [{elems.Count}] :\n");
    
                    for (int i = 0; i < elems.Count; i++)
                    {
                        sb.Append(ObjectToString(elems[i], indent + 4));
                    }
                }
                else
                {
                    Type objType = obj.GetType();
                    PropertyInfo[] props = objType.GetProperties();
    
                    foreach (PropertyInfo prop in props)
                    {
                        if (prop.GetIndexParameters().Length == 0)
                        {
                            object propValue = prop.GetValue(obj);
                            var elems = propValue as IList;
    
                            if (elems != null)
                            {
                                foreach (var item in elems)
                                {
                                    sb.Append($"{indentString}- {prop.Name} :\n");
                                    sb.Append(ObjectToString(item, indent + 4));
                                }
                            }
                            else if (prop.Name != "ExtensionData")
                            {
                                sb.Append($"{indentString}- {prop.Name} = {propValue}\n");
    
                                if (prop.PropertyType.Assembly == objType.Assembly)
                                {
                                    sb.Append(ObjectToString(propValue, indent + 4));
                                }
                            }
                        }
                        else
                        {
                            sb.Append($"{indentString}- {prop.Name} ({prop.PropertyType.Name}): <Indexed>\n");
                        }
                    }
                }
            }
    
            return sb.ToString();
        }
    

    更新 2

    public static string ObjectToString(object obj, int indent = 0)
        {
            var sb = new StringBuilder();
    
            if (obj != null)
            {
                string indentString = new string(' ', indent);
    
                if (obj is string || obj.IsNumber())
                {
                    sb.Append($"{indentString}- {obj}\n");
                }
                else if (obj.GetType().BaseType == typeof(Enum))
                {
                    sb.Append($"{indentString}- {obj.ToString()}\n");
                }
                else if (obj is Array)
                {
                    var elems = obj as IList;
                    sb.Append($"{indentString}- [{elems.Count}] :\n");
    
                    for (int i = 0; i < elems.Count; i++)
                    {
                        sb.Append(ObjectToString(elems[i], indent + 4));
                    }
                }
                else
                {
                    Type objType = obj.GetType();
                    PropertyInfo[] props = objType.GetProperties();
    
                    foreach (PropertyInfo prop in props)
                    {
                        if (prop.GetIndexParameters().Length == 0)
                        {
                            object propValue = prop.GetValue(obj);
                            var elems = propValue as IList;
    
                            if (elems != null)
                            {
                                foreach (var item in elems)
                                {
                                    sb.Append($"{indentString}- {prop.Name} :\n");
                                    sb.Append(ObjectToString(item, indent + 4));
                                }
                            }
                            else if (prop.Name != "ExtensionData")
                            {
                                sb.Append($"{indentString}- {prop.Name} = {propValue}\n");
    
                                if (prop.PropertyType.Assembly == objType.Assembly)
                                {
                                    sb.Append(ObjectToString(propValue, indent + 4));
                                }
                            }
                        }
                        else if (objType.GetProperty("Item") != null)
                        {
                            int count = -1;
    
                            if (objType.GetProperty("Count") != null &&
                                objType.GetProperty("Count").PropertyType == typeof(int))
                            {
                                count = (int)objType.GetProperty("Count").GetValue(obj, null);
                            }
    
                            for (int i = 0; i < count; i++)
                            {
                                object val = prop.GetValue(obj, new object[] { i });
                                sb.Append(ObjectToString(val, indent + 4));
                            }
                        }
                    }
                }
            }
    
            return sb.ToString();
        }
    
        public static bool IsNumber(this object value)
        {
            return value is sbyte
                || value is byte
                || value is short
                || value is ushort
                || value is int
                || value is uint
                || value is long
                || value is ulong
                || value is float
                || value is double
                || value is decimal;
        }
    

    【讨论】:

    • 很抱歉,并非所有属性都已打印出来
    【解决方案4】:

    我已经投入了大量时间来解决这个问题。正确转储所有属性类型需要许多特定于类型的格式。

    我可以建议您查看 github 上此文件中的 FormatValue 方法。将属性值写入字符串的主要逻辑是: https://github.com/thomasgalliker/ObjectDumper/blob/ada64c7e51fedf57731006959358aa890b5e4344/ObjectDumper/Internal/ObjectDumperCSharp.cs#L98

    您还可以将此代码用作 nuget 包: https://www.nuget.org/packages/ObjectDumper.NET

    【讨论】:

      【解决方案5】:

      它适用于所有情况,除了 propValue 是 string[]。您将在行中获得异常“参数计数不匹配”: object propValue = property.GetValue(obj, null);

      要解决此问题,您可以使用此代码稍作修正:

      private void PrintProperties(object obj, int indent)
          {
              if (obj == null) return;
              string indentString = new string(' ', indent);
              Type objType = obj.GetType();
              PropertyInfo[] properties = objType.GetProperties();
              foreach (PropertyInfo property in properties)
              {
                  object propValue = property.GetValue(obj, null);
      
                  var elems = propValue as IList;
                  if ((elems != null) && !(elems is string[]) )
                  {
                      foreach (var item in elems)
                      {
                          PrintProperties(item, indent + 3);
                      }
                  }
                  else
                  {
                      // This will not cut-off System.Collections because of the first check
                      if (property.PropertyType.Assembly == objType.Assembly)
                      {
                          LogToWindow(String.Format("{0}{1}:", indentString, property.Name));
                          PrintProperties(propValue, indent + 2);
                      }
                      else
                      {
                          if (propValue is string[])
                          {
                              var str = new StringBuilder();
                              foreach (string item in (string[])propValue)
                              {
                                  str.AppendFormat("{0}; ", item);
                              }
                              propValue = str.ToString();
                              str.Clear();
                          }
                          LogToWindow(String.Format("{0}{1}: {2}", indentString, property.Name, propValue));
                      }
                  }
              }
          }
      

      【讨论】:

        猜你喜欢
        • 2011-05-12
        • 2019-04-24
        • 1970-01-01
        • 2019-06-27
        • 1970-01-01
        • 1970-01-01
        • 2011-01-23
        • 1970-01-01
        • 2011-03-18
        相关资源
        最近更新 更多