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}");
}