16

Ok so at first I thought this was easy enough, and maybe it is and I'm just too tired - but here's what I'm trying to do. Say I have the following objects:

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

What I need to be able to do, is 'flatten' Containers property names in to a string (including ALL child properties AND child properties of child properties) that would look something like this:

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

Does that make any sense? I can't seem to wrap it around my head.

Zach Johnson
  • 23,678
  • 6
  • 69
  • 86
Wayne
  • 185
  • 1
  • 1
  • 9
  • You have to be clear on how you will determine what is a child property. Here you are assuming that the List type will be flattened out as type T. What if there was a property public Telephone Number { get; set; } (instead of a List)? Would that be handled differently? Will your properties always be either primitive types or List where T is a complex type? – mellamokb Nov 19 '10 at 01:22
  • possible duplicate of [Nested classes and recursion](http://stackoverflow.com/questions/811098/nested-classes-and-recursion) – nawfal Apr 25 '13 at 09:12

2 Answers2

14

I suggest you to mark all the classes, you need to grab, with custom attribute after that you could do something like this

 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
{ }
The Smallest
  • 5,713
  • 25
  • 38
  • @The_Smallest : Hello.. I need something similar to this. But I have the custom attribute added to the properties. I need to get list of all properties which have custom attribute. I don't want to use any attribute at the object level (Extract Name). I am trying to use ur code and make some modifications.But no success. Please help. – Shetty Jun 26 '13 at 14:11
  • @The_Smallest : Hello.. I need something similar to this. But I have the custom attribute added to the properties. I need to get list of all properties which have custom attribute. Since types are nested, i want to get the properties in all inner types as well. I am trying to use ur code and make some modifications.But no success. Please help. – Shetty Jun 26 '13 at 14:40
  • @Shetty Did you try checking if property has your custom attribute right before `var name = property.Name;` something like `if (property.GetCustomAttributes(typeof(), true).Any()) continue;` ? – The Smallest Jun 27 '13 at 20:20
  • Great Solution! but how do I work with Self referencing types In Entity Framework – Joe B Apr 04 '16 at 18:14
0

Per my comment, you could use something like this if it will always be a generic List type that you want to link to a child type. IteratePropertiesRecursively is an iterator over the properties of the given type, that will recursively enumerate the properties of the type and all child types linked through a generic 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;
        }
    }
}

Note: This code will not correctly handle a type that recursively includes itself as a type of one of its properties. Trying to iterate over such a type will result in a StackOverflowException, as pointed out by @Dementic (thanks!).

mellamokb
  • 56,094
  • 12
  • 110
  • 136
  • this throws a StackOverflow. – Rafael Herscovici Nov 15 '12 at 15:21
  • @Dementic: I just tried it again using the OP's original classes. Works fine in LINQPad, and outputs `Container.Name,Container.Address.AddressLine1,Container.Address.AddressLine2,Container.Address.Telephone.CellPhone`. Can you explain where you are getting the SO? Were you testing it on a class type that recursively references itself? If so, that would be one use case where this code shouldn't be used. – mellamokb Nov 15 '12 at 18:01
  • yes, the class i have tried, got an SO, you should fix your code so feature visitors wont get that (that is, cover all bases) – Rafael Herscovici Nov 15 '12 at 20:38
  • 4
    @Dementic: I have added a note to clarify the scope of the answer in the post. I generally seek to answer just the OP's question in my posts, not necessarily provide general-purpose code for all use cases. Nor do I think that is the intention of answers on SO. That's one of the challenges of a site that is used both as a Q&A and a general-purpose programming reference. If you do have a fix to the code that handles recursive cases, feel free to suggest an edit. – mellamokb Nov 15 '12 at 21:11