问题 递归获取对象的属性和子属性


好吧,所以起初我觉得这很容易,也许就是这样,我太累了 - 但这就是我想做的事情。说我有以下对象:

public class Container
{
     public string Name { get; set; }
     public List<Address> Addresses { get; set; }
}
public class Address
{
     public string AddressLine1 { get; set; }
     public string AddressLine2 { get; set; }
     public List<Telephone> Telephones { get; set; }
}
public class Telephone
{
     public string CellPhone { get; set; }
}

我需要做的是,将容器属性名称'flatten'到一个字符串(包括所有子属性和子属性的子属性),如下所示:

Container.Name, Container.Addresses.AddressLine1, Container.Addresses.AddressLine2, Container.Addresses.Telephones.CellPhone

这有任何意义吗?我似乎无法将它包裹在我的脑海里。


891
2017-11-19 00:11


起源

您必须明确如何确定什么是儿童财产。在这里,您假设List <T>类型将被平铺为类型T.如果有属性公共电话号码{get;组; }(而不是List <Telephone>)?会以不同的方式处理吗?您的属性是原始类型还是List <T>,其中T是复杂类型? - mellamokb
可能重复 嵌套类和递归 - nawfal


答案:


我建议你用自定义属性标记所有类,你需要抓取,之后你可以做这样的事情

 class Program
{
    static void Main(string[] args)
    {
        var lines = ExtractHelper.IterateProps(typeof(Container)).ToArray();

        foreach (var line in lines)
            Console.WriteLine(line);

        Console.ReadLine();
    }
}

static class ExtractHelper
{

    public static IEnumerable<string> IterateProps(Type baseType)
    {
        return IteratePropsInner(baseType, baseType.Name);
    }

    private static IEnumerable<string> IteratePropsInner(Type baseType, string baseName)
    {
        var props = baseType.GetProperties();

        foreach (var property in props)
        {
            var name = property.Name;
            var type = ListArgumentOrSelf(property.PropertyType);
            if (IsMarked(type))
                foreach (var info in IteratePropsInner(type, name))
                    yield return string.Format("{0}.{1}", baseName, info);
            else
                yield return string.Format("{0}.{1}", baseName, property.Name);
        }
    }

    static bool IsMarked(Type type)
    {
        return type.GetCustomAttributes(typeof(ExtractNameAttribute), true).Any();
    }


    public static Type ListArgumentOrSelf(Type type)
    {
        if (!type.IsGenericType)
            return type;
        if (type.GetGenericTypeDefinition() != typeof(List<>))
            throw new Exception("Only List<T> are allowed");
        return type.GetGenericArguments()[0];
    }
}

[ExtractName]
public class Container
{
    public string Name { get; set; }
    public List<Address> Addresses { get; set; }
}

[ExtractName]
public class Address
{
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
    public List<Telephone> Telephones { get; set; }
}

[ExtractName]
public class Telephone
{
    public string CellPhone { get; set; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = true, AllowMultiple = true)]
public sealed class ExtractNameAttribute : Attribute
{ }

12
2017-11-19 01:27



谢谢!第一次工作 - Wayne
@The_Smallest:你好..我需要类似的东西。但我已将自定义属性添加到属性中。我需要获取具有自定义属性的所有属性的列表。我不想在对象级别使用任何属性(Extract Name)。我正在尝试使用你的代码并进行一些修改。但没有成功。请帮忙。 - Shetty
@The_Smallest:你好..我需要类似的东西。但我已将自定义属性添加到属性中。我需要获取具有自定义属性的所有属性的列表。由于类型是嵌套的,我想在所有内部类型中获取属性。我正在尝试使用你的代码并进行一些修改。但没有成功。请帮忙。 - Shetty
@Shetty您是否尝试过检查属性是否具有您的自定义属性 var name = property.Name; 就像是 if (property.GetCustomAttributes(typeof(<YOURATTRIBUTEHERE>), true).Any()) continue; ? - The Smallest
好方案!但是如何在实体框架中使用自引用类型 - Joe B


根据我的评论,如果它总是一个你想要链接到子类型的通用List类型,你可以使用这样的东西。 IteratePropertiesRecursively是给定类型属性的迭代器,它将递归枚举类型的属性以及通过泛型List链接的所有子类型。

protected void Test()
{
    Type t = typeof(Container);
    string propertyList = string.Join(",", IteratePropertiesRecursively("", t).ToArray<string>());
    // do something with propertyList
}

protected IEnumerable<string> IteratePropertiesRecursively(string prefix, Type t)
{
    if (!string.IsNullOrEmpty(prefix) && !prefix.EndsWith(".")) prefix += ".";
    prefix += t.Name + ".";

    // enumerate the properties of the type
    foreach (PropertyInfo p in t.GetProperties())
    {
        Type pt = p.PropertyType;

        // if property is a generic list
        if (pt.Name == "List`1")
        {
            Type genericType = pt.GetGenericArguments()[0];
            // then enumerate the generic subtype
            foreach (string propertyName in IteratePropertiesRecursively(prefix, genericType))
            {
                yield return propertyName;
            }
        }
        else
        {
            // otherwise enumerate the property prepended with the prefix
            yield return prefix + p.Name;
        }
    }
}

注意: 此代码将无法正确处理以其递归方式将自身包含为其某个属性类型的类型。试图迭代这样的类型将导致a StackOverflowException,正如@Dementic指出的那样(谢谢!)。


0
2017-11-19 01:39



这会引发StackOverflow。 - Dementic
@Dementic:我刚刚使用OP的原始类再试一次。在LINQPad和输出中正常工作 Container.Name,Container.Address.AddressLine1,Container.Address.AddressLine2,Container.Address.Telephone.CellPhone。你能解释一下你在哪里获得SO吗?你是否在一个递归引用自身的类类型上测试它?如果是这样,那将是一个不应使用此代码的用例。 - mellamokb
是的,我尝试过的课程,得到了一个SO,你应该修改你的代码,以便访问者不会得到它(即覆盖所有基础) - Dementic
@Dementic:我添加了一个注释,以澄清帖子中答案的范围。我通常试图在我的帖子中回答OP的问题,不一定为所有用例提供通用代码。我也不认为这是SO的答案意图。这是一个用作Q&A和通用编程参考的站点的挑战之一。如果您确实修复了处理递归案例的代码,请随时建议编辑。 - mellamokb