C# 使用反射迭代具有嵌套类的类属性
我在这里找到了这个答案,尽管在我的例子中运行它时,它也尝试转储/递归例如C# 使用反射迭代具有嵌套类的类属性,c#,asp.net-core,reflection,C#,Asp.net Core,Reflection,我在这里找到了这个答案,尽管在我的例子中运行它时,它也尝试转储/递归例如字符串的属性,比如名称,当它抛出异常时 我的课是这样的 public class MyModels { public int Id { get; set; } public DateTime EditDate { get; set; } public string EditBy { get; set; } } public class Person { public string Name
字符串
的属性,比如名称
,当它抛出异常时
我的课是这样的
public class MyModels
{
public int Id { get; set; }
public DateTime EditDate { get; set; }
public string EditBy { get; set; }
}
public class Person
{
public string Name { get; set; }
}
public class Organization
{
public Person Person { get; set; }
public Organization()
{
Person = new Person();
}
public string Name { get; set; }
}
public class Company : MyModels
{
public Organization Organization { get; set; }
public Company()
{
Organization = new Organization();
}
public string Description { get; set; }
}
这是链接答案中的代码
var objtree = "";
void DumpObjectTree(object propValue, int level = 0)
{
if (propValue == null)
return;
var childProps = propValue.GetType().GetProperties();
foreach (var prop in childProps)
{
var name = prop.Name;
var value = prop.GetValue(propValue, null);
// add some left padding to make it look like a tree
objtree += ("".PadLeft(level * 4, ' ') + $"{name} = {value}") + Environment.NewLine;
// call again for the child property
DumpObjectTree(value, level + 1);
}
}
DumpObjectTree(itemData);
我想要的是迭代所有属性并检查它们的值 当我运行上述代码示例时:
- 它首先查找组织,然后递归
- 在第1级,它找到
,并递归人
- 在第二级,如果找到
,并递归名称
- 在第三级,当它尝试为
Name
- 它首先查找
,然后递归描述
- 在第一级,当它尝试为
描述
获取值时抛出异常
如何使其不尝试转储/递归类型为
string
,datetime
等的属性,例如Name
,Description
异常消息显示:“参数计数不匹配。”
请注意,objtree
变量中的预期输出/内容为
Organization = MyNameSpace.Models.Organization
Person = MyNameSpace.Models.Person
Name = TestName
Name = TestCompany
Description = Some info about the company...
Id = 1
EditDate = 31/08/2019
EditBy = user@domain.com
出现异常的原因是
string
有一个名为Chars
的属性。您通常不会看到此属性,因为它是在执行类似char c=myString[0]的操作时使用的索引器代码>
这个属性显然需要一个参数(索引),因为您没有提供参数,所以会抛出一个异常
要过滤不希望递归的类型,需要扩展方法中的第一行。比如说
if (propValue == null) return;
if (propValue.GetType().Assembly != Assembly.GetExecutingAssembly())
return;
这将仅通过程序集中声明的类型递归。如果需要特殊过滤,则需要对其进行调整。
您当前的规范(“类型string
,datetime
等”)不够具体,无法给出准确的解决方案,但我认为想法是明确的
请注意,如果在自己的类中声明索引器,这不会阻止引发异常。因此,更好的方法可能是直接检查索引器:
foreach (var prop in childProps)
{
if (prop.GetIndexParameters().Any()) continue;
第二个注意事项:当前代码还有另一个缺陷:您应该跟踪已经转储的类型,并在第二次遇到类型时中止递归。这可能就是在DateTime
发生异常的原因。DateTime
有一个Date
属性,它是DateTime
类型的-hurray。因此,您的objtree
字符串无限增长,直到抛出OutOfMemoryException
或StackOverflowException
。您需要在以下情况下跳过递归:
- 属性是值类型
- 属性是一个字符串
- 属性值包含对上一递归级别(即,
ParentObject
)中对象的引用,因此不会出现堆栈溢出异常
- 编辑:属性为集合类型时也是如此。如果您想获得创造性,可以让递归器迭代集合中的每个对象,然后递归这些对象
这个PropertyInfo
recursor似乎可以做到这一点
[Flags]
public enum PropertyRecursionOverflowProtectionType
{
SkipSameReference,
SkipSameType
}
public class PropertyRecursionBot
{
public object ParentObject { get; set; }
public object CurrentObject { get; set; }
public PropertyInfo PropertyInfo { get; set; }
public Type ParentType { get; set; }
public int Level { get; set; }
}
public static IEnumerable<PropertyRecursionBot> GetAllProperties(object entity,
PropertyRecursionOverflowProtectionType overflowProtectionType = PropertyRecursionOverflowProtectionType.SkipSameReference)
{
var type = entity.GetType();
var bot = new PropertyRecursionBot { CurrentObject = entity };
IEnumerable<PropertyRecursionBot> GetAllProperties(PropertyRecursionBot innerBot, PropertyInfo[] properties)
{
var currentParentObject = innerBot.ParentObject;
var currentObject = innerBot.CurrentObject;
foreach (var pi in properties)
{
innerBot.PropertyInfo = pi;
var obj = pi.GetValue(currentObject);
innerBot.CurrentObject = obj;
//Return the property and value only if it's a value type or string
if (pi.PropertyType == typeof(string) || !pi.PropertyType.IsClass)
{
yield return innerBot;
continue;
}
//This overflow protection check will prevent stack overflow if your object has bidirectional navigation
else if (innerBot.CurrentObject == null ||
(overflowProtectionType.HasFlag(PropertyRecursionOverflowProtectionType.SkipSameReference) && innerBot.CurrentObject == currentParentObject) ||
(overflowProtectionType.HasFlag(PropertyRecursionOverflowProtectionType.SkipSameType) && innerBot.CurrentObject.GetType() == currentParentObject?.GetType()))
{
continue;
}
innerBot.Level++;
innerBot.ParentObject = currentObject;
foreach (var innerPi in GetAllProperties(innerBot, pi.PropertyType.GetProperties()))
{
yield return innerPi;
}
innerBot.Level--;
innerBot.ParentObject = currentParentObject;
innerBot.CurrentObject = obj;
}
}
foreach (var pi in GetAllProperties(bot, type.GetProperties()))
{
yield return pi;
}
}
1) 您希望避免异常(那么请告诉我们是哪种异常,我目前看不出原因),还是2)您通常希望避免转储框架类型还是只转储您自己的类型?在这种情况下,您可以简单地按名称空间或程序集过滤类型。@RenéVogt我添加了我想要的内容,并解释了发生的情况。谢谢您的回答。添加if(propValue.GetType().Assembly!=Assembly.getExecutionGassembly())
后,它不再在例如Name
和Description
属性上递归。当涉及到你的第二个注意事项,跟踪哪些类型被转储时,我不能根据类型来做这件事,因为我的类中有几个属性,例如DateTime
(此处未显示)。但它不会在DateTime
属性上递归,因此不确定该缺陷会如何导致异常。@LGSon因为DateTime
未在程序集中声明,它不再递归到中。@LGSon和不递归类型两次并不意味着不会转储时间值,只有它们的子值将被丢弃。当你说DateTime
没有在你的程序集中声明时,它不再被递归到,我试图理解这实际上意味着什么…类型DateTime
是在mscorlib.dll中声明的,而不是在你的代码中声明的。这就是为什么刚才添加的if语句返回而不是尝试转储DateTime
的属性。
public class RecursionTest
{
public string StringValue { get; set; }
public int IntValue { get; set; }
public RecursionTest Test { get; set; }
public RecursionTest ParentTest { get; set; }
}
var rec1 = new RecursionTest
{
IntValue = 20,
StringValue = Guid.NewGuid().ToString()
};
rec1.Test = new RecursionTest
{
IntValue = 30,
StringValue = Guid.NewGuid().ToString(),
ParentTest = rec1
};
rec1.Test.Test = new RecursionTest
{
IntValue = 40,
StringValue = Guid.NewGuid().ToString(),
ParentTest = rec1.Test
};
foreach (var bot in GetAllProperties(rec1, PropertyRecursionOverflowProtectionType.SkipSameReference))
{
Console.WriteLine($"{new string(' ', bot.Level * 2)}{bot.PropertyInfo.Name}: {bot.CurrentObject}");
}