带递归的 C# 反射

本文关键字:反射 递归 | 更新日期: 2023-09-27 17:57:16

我正在研究反射,但是在执行递归时卡住了。

法典:

public class User {
  public string Name;
  public int Number;
  public Address Address;    
}

public class Address {
 public string Street;
 public string State;
 public string Country;
}

现在我正在打印值。

 Type t = user.GetType();  
 PropertyInfo[] props = t.GetProperties(); 
 foreach (PropertyInfo prp in props)  
 {  
   if(!prp.GetType().IsPrimitive && prp.GetType().IsClass) 
   {
     // Get the values of the Inner Class.
     // i am stucked over here , can anyone help me with this.
           Type ty = prp.GetType();
           var prpI = ty.GetProperties();
           //var tp = ty.GetType().;
            foreach (var propertyInfo in prpI)
            {
            var value = propertyInfo.GetValue(prp);
            var stringValue = (value != null) ? value.ToString() : "";
            console.WriteLine(prp.GetType().Name + "." + propertyInfo.Name+" Value : " +stringValue);    
            }
   }
   else
   {    
     var value = prp.GetValue(user);   
     var stringValue = (value != null) ? value.ToString() : "";
     console.writeline(user.GetType().Name + "." + prp.Name+" Value : " +stringValue); 
   }
 }

我想知道如何找出该属性是类还是基元。 如果它是一个类,则执行递归。

带递归的 C# 反射

首先,如果要访问类型的属性,请确保使用具有属性的类型:

public class User {
  public string Name{get;set;}
  public int Number{get;set;}
  public Address Address{get;set;}    
}

public class Address {
 public string Street{get;set;}
 public string State{get;set;}
 public string Country{get;set;}
}

其次,prp.GetType()总是会PropertyInfo返回。您正在寻找prp.PropertyType,这将返回属性的类型。

此外,if(!prp.GetType().IsPrimitive && prp.GetType().IsClass)不会按照您想要的方式工作,因为例如String是一个类,也不是一个原始词。更好地使用prp.PropertyType.Module.ScopeName != "CommonLanguageRuntimeLibrary"

最后但并非最不重要的一点是,要使用递归,您实际上必须将代码放入方法中。

下面是一个完整的示例:

IEnumerable<string> GetPropertInfos(object o, string parent=null)
{
    Type t = o.GetType();  
    PropertyInfo[] props = t.GetProperties(BindingFlags.Public|BindingFlags.Instance);
    foreach (PropertyInfo prp in props)  
    {  
        if(prp.PropertyType.Module.ScopeName != "CommonLanguageRuntimeLibrary")
        {
            // fix me: you have to pass parent + "." + t.Name instead of t.Name if parent != null
            foreach(var info in GetPropertInfos(prp.GetValue(o), t.Name))
                yield return info; 
        }
        else
        {    
            var value = prp.GetValue(o);   
            var stringValue = (value != null) ? value.ToString() : "";
            var info = t.Name + "." + prp.Name + ": " + stringValue;
            if (String.IsNullOrWhiteSpace(parent))
                yield return info; 
            else
                yield return parent + "." + info; 
        }
    }
}

像这样使用:

var user = new User { Name = "Foo", Number = 19, Address = new Address{ Street="MyStreet", State="MyState",  Country="SomeCountry" }    };
foreach(var info in GetPropertInfos(user))
    Console.WriteLine(info);

它将输出

User.Name: Foo
User.Number: 19
User.Address.Street: MyStreet
User.Address.State: MyState
User.Address.Country: SomeCountry

首先,避免使用反射,除非你真的需要它。它很慢,很乱,几乎无法调试(我喜欢它,但那是另一回事)

如果你想转储对象的全部内容,我建议将其转移到对象本身,它们应该知道它们的内在状态。可以使用对象ToString方法来编写其内部状态,也可以定义用于显示内部状态的接口

interface IStateDisplay
{
   string GetInnerState();
}

并使您的对象实现它。然后,用于显示属性的代码将是

Console.WriteLine(user.GetInnerState());

另一种选择是使用像AutoMapper这样的工具,它向你隐藏反射的复杂性和复杂性,并公开一个很好的API来使用。

但是,如果您正在学习反射,打印复杂对象的状态是一个很好的练习。朝这个方向指出一些:

public string Name;

不是属性,它是一个字段,因此type.GetProperties()不会返回它。阅读什么是 C# 属性,以及如何使用和定义它们。最小属性声明为

public string Name {get; set;}

此外,prp.GetType()将返回PropertyInfo类型的类型信息,而不是它包含的属性的类型信息。在这种情况下,您需要的是prp.PropertyType属性。

接下来,我不认为Type.IsPrimitive检查是你想要的。该属性对于布尔值、字节、SByte、Int16、UInt16、Int32、UInt32、Int64、UInt64、IntPtr、UIntPtr、Char、Double 和 Single 类型返回 true,对于其他所有类型返回 false。最重要的是typeof(string).IsPrimitive返回 false。

同样,我也不认为Type.IsClass支票是你想要的。当您使用它时,仅检查属性是值还是引用类型,并且由于值类型(struct)也可能非常复杂并且包含自己的属性和字段,因此检查没有意义。

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

namespace Extensions
{
    public static class ObjectExtension
    {
        public static string ToStringProperties(this object o)
        {
            return o.ToStringProperties(0);
        }
    public static string ToStringProperties(this object o, int level)
    {
        StringBuilder sb = new StringBuilder();
        string spacer = new String(' ', 2 * level);
        if (level == 0) sb.Append(o.ToString());
        sb.Append(spacer);
        sb.Append("{'r'n");
        foreach (PropertyInfo pi in o.GetType().GetProperties())
        {
        if (pi.GetIndexParameters().Length == 0)
        {
            sb.Append(spacer);
            sb.Append("  ");
            sb.Append(pi.Name);
            sb.Append(" = ");
            object propValue = pi.GetValue(o, null);
            if (propValue == null)
            {
                sb.Append(" <null>");
            } else {
                if (IsMyOwnType(pi.PropertyType))
                {
                    sb.Append("'r'n");
                    sb.Append(((object)propValue).ToStringProperties(level + 1));
                } else{
                    sb.Append(propValue.ToString());
                }
            }
            sb.Append("'r'n");
        }
    }
    sb.Append(spacer);
    sb.Append("}'r'n");
    return sb.ToString();
}
    private static bool IsMyOwnType(Type t) 
{
    return (t.Assembly == Assembly.GetExecutingAssembly());
}
}
}

谢谢@Sloth,你的代码非常有用。对于那些收到"对象引用未设置为对象的实例"错误的人来说,这是一个轻微的修改。它创建对象的实例为 null,并处理数组。必须做更多的工作来处理所有可能的集合类型,但这是一个开始。

public static IEnumerable<string> GetPropertInfos(object o, string parent = null)
    {

        Type t = o.GetType();
        //   String namespaceValue = t.Namespace;

        PropertyInfo[] props = t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
        foreach (PropertyInfo prp in props)
        {
            if (prp.PropertyType.Module.ScopeName != "CommonLanguageRuntimeLibrary")
            {
                // fix me: you have to pass parent + "." + t.Name instead of t.Name if parent != null
                object value = prp.GetValue(o);
                if (value == null)
                {
                    value =
                        Activator.CreateInstance(Type.GetType(
                            (prp.PropertyType).AssemblyQualifiedName.Replace("[]", "")));
                }
                var propertInfos = GetPropertInfos(value, t.Name);
                foreach (var info in propertInfos)
                    yield return info;
            }
            else
            {
                var type = GetTypeName(prp);
                var info = t.Name + "." + prp.Name ;
                if (String.IsNullOrWhiteSpace(parent))
                    yield return info;
                else
                    yield return parent + "." + info;
            }
        }
    }
您可以使用

Type.IsValueType 属性。