Рекурсивно Получить свойства и дочерние свойства объекта

Итак, сначала я подумал, что это достаточно легко, и, возможно, это так, и я просто слишком устал, но вот что я пытаюсь сделать. Скажем, у меня есть следующие объекты:

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

Что мне нужно сделать, это "сгладить" имена свойств контейнера в строке (включая ВСЕ дочерние свойства и дочерние свойства дочерних свойств), которые выглядели бы примерно так:

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

В этом смысл? Кажется, я не могу обернуть его вокруг головы.

Ответ 1

Я предлагаю вам отметить все классы, которые нужно захватить, с помощью настраиваемого атрибута, после чего вы могли бы сделать что-то вроде этого

 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
{ }

Ответ 2

В моем комментарии вы можете использовать что-то вроде этого, если оно всегда будет общим типом списка, который вы хотите связать с типом дочернего элемента. IteratePropertiesRecursively - это итератор по свойствам данного типа, который будет рекурсивно перечислять свойства типа и всех дочерних типов, связанных через общий список.

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

Примечание.. Этот код неправильно обрабатывает тип, который рекурсивно включает в себя тип одного из его свойств. Попытка итерации по такому типу приведет к StackOverflowException, как указано @Dementic (спасибо!).