4

I'm trying to fetch all ICollection<T> properties from class of unknown type. Also, type T (what the collection is of) is not known at compile time. Firstly I've tried this approach:

foreach (var property in entity.GetType().GetProperties())
{
    if (typeof(ICollection).IsAssignableFrom(property.PropertyType) || typeof(ICollection<>).IsAssignableFrom(property.PropertyType))
    {
        // do something
    }
}

but it's not working (evaluating false even for ICollection properties).

I got it working like this:

foreach (var property in entity.GetType().GetProperties())
{
    var getMethod = property.GetGetMethod();
    var test = getMethod.Invoke(entity, null);
    if (test is ICollection)
    {
        // do something
    }
}

but I do not want to execute all getters. Why is the first piece of code not working? How can I find ICollection properties without executing all getters?

xx77aBs
  • 4,678
  • 9
  • 53
  • 77
  • 2
    `ICollection` and `ICollection` are different types. Which are you actually interested in? – Jon Skeet Jul 06 '14 at 16:52
  • I am interested only in ICollection properties (e.g. public virtual ICollection Notes { get; set; }). I'm trying to solve Adding/Updating objects with many-to-many relations when using EF and POCO (detached) objects. For that I need to get ICollection properties for any class (not known) and then get value of that property, i.e. elements of the collection. – xx77aBs Jul 06 '14 at 17:02
  • Reflection is silly slow. Have you considered using T4 for the code gen? This however feels very XY and i suspect you could solve most of your real issues with generics...PS have you considered how you are going to deal with cyclic relationships? – Aron Jul 07 '14 at 18:57

2 Answers2

5

It turns out that with IsAssignableFrom check you can't find whether the interface is a derivative of another interface:

Console.WriteLine(typeof(ICollection<>).IsAssignableFrom(typeof(ICollection<Int32>)));     
Console.WriteLine(typeof(ICollection<Int32>).IsAssignableFrom(typeof(ICollection<>)));

will both write false;

With little help from here this is the best solution I can come of:

    static IEnumerable<PropertyInfo> GetICollectionOrICollectionOfTProperties(this Type type)
    {    
        // Get properties with PropertyType declared as interface
        var interfaceProps =
            from prop in type.GetProperties()
            from interfaceType in prop.PropertyType.GetInterfaces()
            where interfaceType.IsGenericType
            let baseInterface = interfaceType.GetGenericTypeDefinition()
            where (baseInterface == typeof(ICollection<>)) || (baseInterface == typeof(ICollection))
            select prop;

        // Get properties with PropertyType declared(probably) as solid types.
        var nonInterfaceProps =
            from prop in type.GetProperties()
            where typeof(ICollection).IsAssignableFrom(prop.PropertyType) || typeof(ICollection<>).IsAssignableFrom(prop.PropertyType)
            select prop;

        // Combine both queries into one resulting
        return interfaceProps.Union(nonInterfaceProps);                
    }

This solution may yield some duplicates(it is hardly possible, but to be sure use Distinct) and it doesn't look very nice.

But it works well on such class with properties with both the interface return types and concrete return types :

    class Collections
    {
        public List<Int32> ListTProp
        {
            get;
            set;
        }

        public IDictionary<Int32, String> IDictionaryProp
        {
            get;
            set;
        }

        public ICollection ICollectionProp
        {
            get;
            set;
        }

        public ICollection<DateTime> IDateTimeCollectionProp
        {
            get;
            set;
        }
    }
Community
  • 1
  • 1
Eugene Podskal
  • 10,270
  • 5
  • 31
  • 53
  • @Askolein Perhaps, it doesn't solve the exact problem that OP faced or solves it in a way he'd prefer not to use. Or, perhaps, he just forgot to mark it as accepted. Who knows. – Eugene Podskal Apr 16 '15 at 09:47
1

After attempting to use the accepted answer I had a situation where only partial matches where being returned. My object had 3 ICollection<T> properties and I was only being returned with 2. I spent some time testing and trying to figure out why, but I moved on and wrote this:

public static IEnumerable<PropertyInfo> GetICollectionProperties(object entity) 
{
    return entity.GetType().GetProperties()
            .Where(p => p.PropertyType.IsGenericType
            && p.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>));
}

I have tested with the same test cases and I am getting the correct results returned from this method.

This will not pick up non-generic ICollections, but the OP did ask for ICollection<T>properties, all-though it could be easily re-factored to include. It will also not return properties that are not exactly of type ICollection (ie, Eugene's List and IDictionary in his test case would not be returned (but again, what the OP wanted)).

Neil Watson
  • 2,801
  • 3
  • 18
  • 20