带递归的 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);
}
}
我想知道如何找出该属性是类还是基元。 如果它是一个类,则执行递归。
首先,如果要访问类型的属性,请确保使用具有属性的类型:
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
属性。